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
import kotlinx.coroutines.*
import net.mamoe.mirai.Bot
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.BotImpl
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.ImageId0x03
import net.mamoe.mirai.message.data.ImageId0x06
import net.mamoe.mirai.network.data.*
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.timpc.internal.RawGroupInfo
import net.mamoe.mirai.timpc.network.GroupImpl
import net.mamoe.mirai.timpc.network.MemberImpl
import net.mamoe.mirai.timpc.network.QQImpl
import net.mamoe.mirai.timpc.network.TIMBotNetworkHandler
import net.mamoe.mirai.timpc.network.handler.TemporaryPacketHandler
import net.mamoe.mirai.timpc.network.packet.KnownPacketId
import net.mamoe.mirai.timpc.network.packet.OutgoingPacket
import net.mamoe.mirai.timpc.network.packet.SessionKey
import net.mamoe.mirai.timpc.network.packet.action.*
import net.mamoe.mirai.timpc.utils.assertUnreachable
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
import net.mamoe.mirai.utils.io.logStacktrace
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext
import kotlin.jvm.JvmSynthetic
internal expect class TIMPCBot constructor(
account: BotAccount,
logger: MiraiLogger?,
context: CoroutineContext
) : TIMPCBotBase
@UseExperimental(MiraiInternalAPI::class)
internal abstract class TIMPCBotBase constructor(
account: BotAccount,
logger: MiraiLogger?,
context: CoroutineContext
) : BotImpl<TIMBotNetworkHandler>(account, logger ?: DefaultLogger("Bot(" + account.id + ")"), context) {
companion object {
init {
KnownPacketId.values() /* load id classes */
}
}
final override val network: TIMBotNetworkHandler get() = _network
inline val sessionKey: SessionKey get() = network.sessionKey
private lateinit var _network: TIMBotNetworkHandler
override suspend fun login(configuration: BotConfiguration) =
reinitializeNetworkHandler(configuration, null)
// shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close
internal fun tryReinitializeNetworkHandler(
configuration: BotConfiguration,
cause: Throwable?
): Job = launch {
repeat(configuration.reconnectionRetryTimes) {
try {
reinitializeNetworkHandler(configuration, cause)
logger.info("Reconnected successfully")
return@launch
} catch (e: LoginFailedException){
delay(configuration.reconnectPeriodMillis)
}
}
}
private suspend fun reinitializeNetworkHandler(
configuration: BotConfiguration,
cause: Throwable?
) {
logger.info("BotAccount: $qqAccount")
logger.info("Initializing BotNetworkHandler")
try {
if (::_network.isInitialized) {
_network.close(cause)
}
} catch (e: Exception) {
logger.error("Cannot close network handler", e)
}
_network = TIMBotNetworkHandler(this.coroutineContext + configuration, this as TIMPCBot)
_network.login()
}
final override suspend fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult {
return when (CanAddFriendPacket(qqAccount, id, sessionKey).sendAndExpect<CanAddFriendResponse>()) {
is CanAddFriendResponse.AlreadyAdded -> AddFriendResult.ALREADY_ADDED
is CanAddFriendResponse.Rejected -> AddFriendResult.REJECTED
is CanAddFriendResponse.ReadyToAdd,
is CanAddFriendResponse.RequireVerification -> {
val key = RequestFriendAdditionKeyPacket(qqAccount, id, sessionKey).sendAndExpect<RequestFriendAdditionKeyPacket.Response>().key
AddFriendPacket.RequestAdd(qqAccount, id, sessionKey, message, remark, key).sendAndExpect<AddFriendPacket.Response>()
AddFriendResult.WAITING_FOR_APPROVE
} //这个做的是需要验证消息的情况, 不确定 ReadyToAdd 的是啥
// 似乎 RequireVerification 和 ReadyToAdd 判断错了. 需要重新检查一下
// TODO: 2019/11/11 需要验证问题的情况
/*is CanAddFriendResponse.ReadyToAdd -> {
// TODO: 2019/11/11 这不需要验证信息的情况
//AddFriendPacket(qqAccount, id, sessionKey, ).sendAndExpectAsync<AddFriendPacket.Response>().await()
TODO()
}*/
}
}
final override suspend fun approveFriendAddRequest(id: Long, remark: String?) {
AddFriendPacket.Approve(qqAccount, sessionKey, 0, id, remark).sendAndExpect<AddFriendPacket.Response>()
}
// region contacts
final override val groups: ContactList<Group> = ContactList(LockFreeLinkedList())
final override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList())
/**
* 线程安全地获取缓存的 QQ 对象. 若没有对应的缓存, 则会创建一个.
*/
@UseExperimental(MiraiInternalAPI::class)
@JvmSynthetic
final override fun getQQ(id: Long): QQ = qqs.delegate.getOrAdd(id) { QQ(id) }
@UseExperimental(MiraiInternalAPI::class, ExperimentalUnsignedTypes::class)
final override suspend fun getGroup(id: GroupId): Group = groups.delegate.getOrNull(id.value) ?: inline {
val info: RawGroupInfo = try {
when (val response =
GroupPacket.QueryGroupInfo(qqAccount, id.toInternalId(), sessionKey).sendAndExpect<GroupPacket.InfoResponse>()) {
is RawGroupInfo -> response
is GroupNotFound -> throw GroupNotFoundException("id=${id.value}")
else -> assertUnreachable()
}
} catch (e: Exception) {
throw IllegalStateException("Cannot obtain group info for id ${id.value}", e)
}
return groups.delegate.getOrAdd(id.value) { Group(id, info) }
}
final override suspend fun getGroup(internalId: GroupInternalId): Group =
getGroup0(internalId.toId().value)
private suspend inline fun getGroup0(id: Long): Group =
groups.delegate.getOrNull(id) ?: inline {
val info: RawGroupInfo = try {
GroupPacket.QueryGroupInfo(qqAccount, GroupId(id).toInternalId(), sessionKey).sendAndExpect()
} catch (e: Exception) {
e.logStacktrace()
error("Cannot obtain group info for id $id")
}
return groups.delegate.getOrAdd(id) { Group(GroupId(id), info) }
}
@UseExperimental(MiraiInternalAPI::class)
final override suspend fun getGroup(id: Long): Group = getGroup0(id.coerceAtLeastOrFail(0))
internal suspend inline fun <reified P : Packet, R> OutgoingPacket.sendAndExpectAsync(
checkSequence: Boolean = true,
noinline handler: suspend (P) -> R
): Deferred<R> {
val deferred: CompletableDeferred<R> = CompletableDeferred(coroutineContext[Job])
network.temporaryPacketHandlers.addLast(
TemporaryPacketHandler(
expectationClass = P::class,
deferred = deferred,
checkSequence = if (checkSequence) this.sequenceId else null,
callerContext = coroutineContext + deferred,
handler = handler
)
)
network.socket.sendPacket(this)
return deferred
}
internal suspend inline fun <reified P : Packet> OutgoingPacket.sendAndExpectAsync(checkSequence: Boolean = true): Deferred<P> =
sendAndExpectAsync<P, P>(checkSequence) { it }
internal suspend inline fun <reified P : Packet, R> OutgoingPacket.sendAndExpect(
checkSequence: Boolean = true,
timeoutMillis: Long = 5.secondsToMillis,
crossinline mapper: (P) -> R
): R = withTimeout(timeoutMillis) { sendAndExpectAsync<P, R>(checkSequence) { mapper(it) }.await() }
internal suspend inline fun <reified P : Packet> OutgoingPacket.sendAndExpect(
checkSequence: Boolean = true,
timeoutMillist: Long = 5.secondsToMillis
): P = withTimeout(timeoutMillist) { sendAndExpectAsync<P, P>(checkSequence) { it }.await() }
internal suspend inline fun OutgoingPacket.send() = network.socket.sendPacket(this)
final override suspend fun Image.getLink(): ImageLink = when (val id = this.id) {
is ImageId0x03 -> GroupImagePacket.RequestImageLink(qqAccount, sessionKey, id).sendAndExpect<GroupImageLink>().requireSuccess()
is ImageId0x06 -> FriendImagePacket.RequestImageLink(qqAccount, sessionKey, id).sendAndExpect<FriendImageLink>()
else -> assertUnreachable()
}
@Suppress("FunctionName")
@PublishedApi
internal fun CoroutineScope.Group(groupId: GroupId, info: RawGroupInfo): Group {
return GroupImpl(this@TIMPCBotBase as TIMPCBot, groupId, coroutineContext).apply { this.info = info.parseBy(this); launch { startUpdater() } }
}
@Suppress("FunctionName")
@PublishedApi
internal fun Group.Member(qq: QQ, permission: MemberPermission): Member {
return MemberImpl(qq, this, permission, coroutineContext).apply { launch { startUpdater() } }
}
@Suppress("FunctionName")
internal fun CoroutineScope.QQ(id: Long): QQ =
QQImpl(this@TIMPCBotBase as TIMPCBot, id, coroutineContext).apply { launch { startUpdater() } }
}
internal inline fun <R> inline(block: () -> R): R = block()
internal suspend fun TIMPCBot.sendPacket(toSend: OutgoingPacket) = this.network.socket.sendPacket(toSend)
/**
* 以 [Bot] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值.
* 这个方法将能帮助使用在 [Bot] 中定义的一些扩展方法
*/
@UseExperimental(ExperimentalContracts::class)
internal inline fun <R> Contact.withTIMPCBot(block: TIMPCBot.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return (bot as TIMPCBot).run(block)
}
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 kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.io.core.* import kotlinx.io.core.*
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.* import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.event.events.BeforePacketSendEvent import net.mamoe.mirai.event.Cancellable
import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.BotLoginSucceedEvent import net.mamoe.mirai.event.events.BotLoginSucceedEvent
import net.mamoe.mirai.event.events.PacketSentEvent
import net.mamoe.mirai.event.events.ServerPacketReceivedEvent
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.network.data.LoginResult
import net.mamoe.mirai.network.protocol.timpc.handler.DataPacketSocketAdapter import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.network.protocol.timpc.handler.TemporaryPacketHandler
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.network.protocol.timpc.packet.login.*
import net.mamoe.mirai.qqAccount import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.OnlineStatus import net.mamoe.mirai.timpc.TIMPCBot
import net.mamoe.mirai.utils.currentBotConfiguration import net.mamoe.mirai.timpc.network.handler.DataPacketSocketAdapter
import net.mamoe.mirai.timpc.network.handler.TemporaryPacketHandler
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.timpc.network.packet.login.*
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.properties.Delegates
import kotlin.random.Random import kotlin.random.Random
/** /**
...@@ -39,9 +37,9 @@ internal expect val NetworkDispatcher: CoroutineDispatcher ...@@ -39,9 +37,9 @@ internal expect val NetworkDispatcher: CoroutineDispatcher
* *
* @see BotNetworkHandler * @see BotNetworkHandler
*/ */
internal class TIMBotNetworkHandler internal constructor(coroutineContext: CoroutineContext, override val bot: Bot) : internal class TIMBotNetworkHandler internal constructor(coroutineContext: CoroutineContext, bot: TIMPCBot) :
BotNetworkHandler<TIMBotNetworkHandler.BotSocketAdapter>, CoroutineScope { BotNetworkHandler(), CoroutineScope {
override val bot: TIMPCBot by bot.unsafeWeakRef()
override val supervisor: CompletableJob = SupervisorJob(coroutineContext[Job]) override val supervisor: CompletableJob = SupervisorJob(coroutineContext[Job])
override val coroutineContext: CoroutineContext = override val coroutineContext: CoroutineContext =
...@@ -50,52 +48,43 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou ...@@ -50,52 +48,43 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
?: "an unnamed coroutine"} under TIMBotNetworkHandler", e) ?: "an unnamed coroutine"} under TIMBotNetworkHandler", e)
} + supervisor } + supervisor
override lateinit var socket: BotSocketAdapter lateinit var socket: BotSocketAdapter
private set private set
private val temporaryPacketHandlers = mutableListOf<TemporaryPacketHandler<*, *>>() internal val temporaryPacketHandlers = LockFreeLinkedList<TemporaryPacketHandler<*, *>>()
private val handlersLock = Mutex()
private var heartbeatJob: Job? = null private var heartbeatJob: Job? = null
suspend fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*, *>) { override suspend fun login() {
handlersLock.withLock {
temporaryPacketHandlers.add(temporaryPacketHandler)
}
temporaryPacketHandler.send(this.session)
}
override suspend fun login(): LoginResult { TIMProtocol.SERVER_IP.sortedBy { Random.nextInt() }.forEach { ip ->
return withContext(this.coroutineContext) { bot.logger.info("Connecting server $ip")
TIMProtocol.SERVER_IP.sortedBy { Random.nextInt() }.forEach { ip -> try {
bot.logger.info("Connecting server $ip") withTimeout(3000) {
try { socket = BotSocketAdapter(ip)
withTimeout(3000) {
socket = BotSocketAdapter(ip)
}
} catch (e: Exception) {
return@withContext LoginResult.NETWORK_UNAVAILABLE
} }
} catch (e: Exception) {
throw LoginFailedException(LoginResult.NETWORK_UNAVAILABLE)
}
loginResult = CompletableDeferred() loginResult = CompletableDeferred()
socket.resendTouch().takeIf { it != LoginResult.TIMEOUT }?.let { return@withContext it } val result = socket.resendTouch() ?: return // success
result.takeIf { it != LoginResult.TIMEOUT }?.let { throw LoginFailedException(it) }
bot.logger.warning("Timeout. Retrying next server") bot.logger.warning("Timeout. Retrying next server")
socket.close() socket.close()
}
return@withContext LoginResult.TIMEOUT
} }
throw LoginFailedException(LoginResult.TIMEOUT)
} }
internal var loginResult: CompletableDeferred<LoginResult> = CompletableDeferred() internal var loginResult: CompletableDeferred<LoginResult?> = CompletableDeferred()
override lateinit var session: BotSession
//private | internal //private | internal
private var sessionKey: SessionKey by Delegates.notNull() private var _sessionKey: SessionKey? = null
internal val sessionKey: SessionKey get() = _sessionKey ?: error("sessionKey is not yet initialized")
override suspend fun awaitDisconnection() { override suspend fun awaitDisconnection() {
heartbeatJob?.join() heartbeatJob?.join()
...@@ -187,43 +176,38 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou ...@@ -187,43 +176,38 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
} }
} }
internal suspend fun resendTouch(): LoginResult /* = coroutineScope */ { internal suspend fun resendTouch(): LoginResult? /* = coroutineScope */ {
loginHandler?.close() loginHandler?.close()
loginHandler = LoginHandler() loginHandler = LoginHandler()
val expect = expectPacket<TouchPacket.TouchResponse>() expectingTouchResponse = Job(supervisor)
launch { processReceive() } try {
launch { launch { processReceive() }
if (withTimeoutOrNull(currentBotConfiguration().touchTimeoutMillis) { expect.join() } == null) { launch {
loginResult.complete(LoginResult.TIMEOUT) if (withTimeoutOrNull(currentBotConfiguration().touchTimeoutMillis) { expectingTouchResponse!!.join() } == null) {
loginResult.complete(LoginResult.TIMEOUT)
}
} }
} sendPacket(TouchPacket(bot.qqAccount, serverIp, false))
sendPacket(TouchPacket(bot.qqAccount, serverIp, false))
return loginResult.await() return loginResult.await()
} } finally {
expectingTouchResponse = null
private suspend inline fun <reified P : Packet> expectPacket(): CompletableDeferred<P> {
val receiving = CompletableDeferred<P>(coroutineContext[Job])
subscribe<ServerPacketReceivedEvent<*>> {
if (it.packet is P && it.bot === bot) {
receiving.complete(it.packet)
ListeningStatus.STOPPED
} else
ListeningStatus.LISTENING
} }
return receiving
} }
private var expectingTouchResponse: CompletableJob? = null
private suspend fun <TPacket : Packet> handlePacket0( private suspend fun <TPacket : Packet> handlePacket0(
sequenceId: UShort, sequenceId: UShort,
packet: TPacket, packet: TPacket,
factory: PacketFactory<TPacket, *> factory: PacketFactory<TPacket, *>
) { ) {
if (ServerPacketReceivedEvent(bot, packet).broadcast().cancelled) if (packet is TouchPacket.TouchResponse) {
return expectingTouchResponse?.complete()
}
if (!packet::class.annotations.filterIsInstance<NoLog>().any()) { if (!packet::class.annotations.filterIsInstance<NoLog>().any()) {
if ((packet as? BroadcastControllable)?.shouldBroadcast != false) { if ((packet as? BroadcastControllable)?.shouldBroadcast != false) {
...@@ -236,12 +220,10 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou ...@@ -236,12 +220,10 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
is Subscribable -> if ((packet as? BroadcastControllable)?.shouldBroadcast != false) packet.broadcast() is Subscribable -> if ((packet as? BroadcastControllable)?.shouldBroadcast != false) packet.broadcast()
} }
// Remove first to release the lock temporaryPacketHandlers.forEach {
handlersLock.withLock { if (it.filter(packet, sequenceId) && temporaryPacketHandlers.remove(it)) {
temporaryPacketHandlers.filter { it.filter(session, packet, sequenceId) } it.doReceivePassingExceptionsToDeferred(packet)
.also { temporaryPacketHandlers.removeAll(it) } }
}.forEach {
it.doReceiveCatchingExceptions(packet)
} }
if (factory is SessionPacketFactory<*>) { if (factory is SessionPacketFactory<*>) {
...@@ -256,10 +238,6 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou ...@@ -256,10 +238,6 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
internal suspend fun sendPacket(packet: OutgoingPacket): Unit = withContext(coroutineContext + CoroutineName("sendPacket")) { internal suspend fun sendPacket(packet: OutgoingPacket): Unit = withContext(coroutineContext + CoroutineName("sendPacket")) {
check(channel.isOpen) { "channel is not open" } check(channel.isOpen) { "channel is not open" }
if (BeforePacketSendEvent(bot, packet).broadcast().cancelled) {
return@withContext
}
packet.delegate.use { built -> packet.delegate.use { built ->
val buffer = IoBuffer.Pool.borrow() val buffer = IoBuffer.Pool.borrow()
try { try {
...@@ -291,8 +269,6 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou ...@@ -291,8 +269,6 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
bot.logger.verbose("Packet sent: ${it.name}") bot.logger.verbose("Packet sent: ${it.name}")
} }
PacketSentEvent(bot, packet).broadcast()
Unit Unit
} }
...@@ -466,8 +442,8 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou ...@@ -466,8 +442,8 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
} }
is RequestSessionPacket.SessionKeyResponse -> { is RequestSessionPacket.SessionKeyResponse -> {
sessionKey = packet.sessionKey _sessionKey = packet.sessionKey
bot.logger.info("sessionKey = ${sessionKey.value.toUHexString()}") bot.logger.info("sessionKey = ${packet.sessionKey.value.toUHexString()}")
setOnlineStatus(OnlineStatus.ONLINE)//required setOnlineStatus(OnlineStatus.ONLINE)//required
} }
...@@ -475,14 +451,11 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou ...@@ -475,14 +451,11 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
is ChangeOnlineStatusPacket.ChangeOnlineStatusResponse -> { is ChangeOnlineStatusPacket.ChangeOnlineStatusResponse -> {
BotLoginSucceedEvent(bot).broadcast() BotLoginSucceedEvent(bot).broadcast()
session = BotSession(sessionKey)
val configuration = currentBotConfiguration() val configuration = currentBotConfiguration()
heartbeatJob = this@TIMBotNetworkHandler.launch { heartbeatJob = this@TIMBotNetworkHandler.launch {
while (socket.isOpen) { while (socket.isOpen) {
delay(configuration.heartbeatPeriodMillis) delay(configuration.heartbeatPeriodMillis)
with(session) { with(bot) {
class HeartbeatTimeoutException : CancellationException("heartbeat timeout") class HeartbeatTimeoutException : CancellationException("heartbeat timeout")
if (withTimeoutOrNull(configuration.heartbeatTimeoutMillis) { if (withTimeoutOrNull(configuration.heartbeatTimeoutMillis) {
...@@ -500,7 +473,7 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou ...@@ -500,7 +473,7 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
} }
bot.logger.info("Successfully logged in") bot.logger.info("Successfully logged in")
loginResult.complete(LoginResult.SUCCESS) loginResult.complete(null)
this.close()//The LoginHandler is useless since then this.close()//The LoginHandler is useless since then
} }
} }
......
@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
@file:JvmMultifileClass
@file:JvmName("BotHelperKt")
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai
import net.mamoe.mirai.network.protocol.timpc.packet.action.AddFriendPacket
import net.mamoe.mirai.network.protocol.timpc.packet.action.CanAddFriendPacket
import net.mamoe.mirai.network.protocol.timpc.packet.action.CanAddFriendResponse
import net.mamoe.mirai.network.protocol.timpc.packet.action.RequestFriendAdditionKeyPacket
import net.mamoe.mirai.network.sessionKey
import kotlin.contracts.ExperimentalContracts
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
@Suppress("ClassName")
sealed class AddFriendResult {
abstract class DONE internal constructor() : AddFriendResult() {
override fun toString(): String = "AddFriendResult(Done)"
}
/**
* 对方拒绝添加好友
*/
object REJECTED : AddFriendResult() {
override fun toString(): String = "AddFriendResult(Rejected)"
}
/**
* 这个人已经是好友
*/
object ALREADY_ADDED : DONE() {
override fun toString(): String = "AddFriendResult(AlreadyAdded)"
}
/**
* 等待对方同意
*/
object WAITING_FOR_APPROVE : DONE() {
override fun toString(): String = "AddFriendResult(WaitingForApprove)"
}
/**
* 成功添加 (只在对方设置为允许任何人直接添加为好友时才会获得这个结果)
*/
object ADDED : DONE() {
override fun toString(): String = "AddFriendResult(Added)"
}
}
/*
// TODO: 2019/11/11 其中一个是对方已同意添加好友的包
Mirai 22:04:48 : Packet received: UnknownEventPacket(id=00 BC, identity=(2092749761->1994701021))
= 00 00 00 08 00 0A 00 04 01 00 00 00 01 00 00 0F 00 00 00 00 00 00 00 00 01 03 EB 00 02 0A 00
Mirai 22:04:48 : Packet received: UnknownEventPacket(id=00 D6, identity=(2092749761->1994701021))
= 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 00 BC 01 00 00 00 00 02 00 00
*/
/**
* 添加一个好友
*
* @param message 若需要验证请求时的验证消息.
* @param remark 好友备注
*/
@UseExperimental(ExperimentalContracts::class)
suspend fun Bot.addFriend(id: UInt, message: String? = null, remark: String? = null): AddFriendResult = withSession {
return when (CanAddFriendPacket(qqAccount, id, sessionKey).sendAndExpect<CanAddFriendResponse>()) {
is CanAddFriendResponse.AlreadyAdded -> AddFriendResult.ALREADY_ADDED
is CanAddFriendResponse.Rejected -> AddFriendResult.REJECTED
is CanAddFriendResponse.ReadyToAdd,
is CanAddFriendResponse.RequireVerification -> {
val key = RequestFriendAdditionKeyPacket(qqAccount, id, sessionKey).sendAndExpect<RequestFriendAdditionKeyPacket.Response>().key
AddFriendPacket.RequestAdd(qqAccount, id, sessionKey, message, remark, key).sendAndExpect<AddFriendPacket.Response>()
AddFriendResult.WAITING_FOR_APPROVE
} //这个做的是需要验证消息的情况, 不确定 ReadyToAdd 的是啥
// 似乎 RequireVerification 和 ReadyToAdd 判断错了. 需要重新检查一下
// TODO: 2019/11/11 需要验证问题的情况
/*is CanAddFriendResponse.ReadyToAdd -> {
// TODO: 2019/11/11 这不需要验证信息的情况
//AddFriendPacket(qqAccount, id, sessionKey, ).sendAndExpectAsync<AddFriendPacket.Response>().await()
TODO()
}*/
}
}
/*
1494695429 同意好友请求后收到以下包:
Mirai 22:11:14 : Packet received: UnknownEventPacket(id=02 10, identity=(1994701021->1994701021))
= 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 06 00 00 00 08 08 02 1A 02 08 44 2A 06 08 83 D8 A5 EE 05
Mirai 22:12:06 : Packet received: UnknownEventPacket(id=02 02, identity=(1994701021->1994701021))
= 00 00 00 08 00 0A 00 04 01 00 00 00 02 00 00
Mirai 22:12:06 : Packet received: UnknownEventPacket(id=00 D6, identity=(1494695429->1994701021))
= 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 00 BC 01 00 00 00 00 02 00 00
Mirai 22:12:06 : Packet received: UnknownEventPacket(id=00 BC, identity=(1494695429->1994701021))
= 00 00 00 08 00 0A 00 04 01 00 00 00 01 00 00 0F 00 00 00 00 00 00 00 00 01 03 EB 00 02 0A 00
Mirai 22:12:06 : Packet received: UnknownEventPacket(id=02 10, identity=(1994701021->1994701021))
//�9����同意你的加好友请求"him188的小dick(
= 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 07 00 00 00 3B 08 02 1A 03 08 E2 01 0A 39 08 01 10 85 FC DC C8 05 1A 1B E5 90 8C E6 84 8F E4 BD A0 E7 9A 84 E5 8A A0 E5 A5 BD E5 8F 8B E8 AF B7 E6 B1 82 22 10 68 69 6D 31 38 38 E7 9A 84 E5 B0 8F 64 69 63 6B 28 01
Mirai 22:12:06 : Packet received: UnknownEventPacket(id=02 10, identity=(1994701021->1994701021))
= 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 06 00 00 00 08 08 02 1A 02 08 44 2A 06 08 B7 D8 A5 EE 05
Mirai 22:12:06 : Packet received: UnknownEventPacket(id=02 10, identity=(1994701021->1994701021))
= 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 06 00 00 00 04 08 02 1A 02 08 23 1A 02 08 00
Mirai 22:12:06 : Packet received: UnknownEventPacket(id=02 10, identity=(1994701021->1994701021))
= 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 07 00 00 00 0C 08 02 1A 03 08 E2 01 0A 0A 08 00 10 DD F1 92 B7 07 1A 00
Mirai 22:12:06 : Packet received: UnknownEventPacket(id=02 10, identity=(1994701021->1994701021))
= 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 06 00 00 00 18 08 02 1A 02 08 44 1A 0E 08 DD F1 92 B7 07 10 B7 D8 A5 EE 05 18 01 2A 06 08 B7 D8 A5 EE 05
Mirai 22:12:06 : Packet received: UnknownEventPacket(id=02 10, identity=(1494695429->1994701021))
//来自QQ号查找:BJR
= 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 06 00 00 00 31 08 02 1A 02 08 23 12 2F 08 DD F1 92 B7 07 10 85 FC DC C8 05 18 03 20 82 D8 A5 EE 05 2A 11 E6 9D A5 E8 87 AA 51 51 E5 8F B7 E6 9F A5 E6 89 BE 3A 00 42 00 4A 00 52 00
Mirai 22:12:07 : Packet received: UnknownPacket(01 2D)
body=01 01 00 00 00 01 59 17 3E 05 00 00 00
Mirai 22:12:07 : UnknownPacket(01 2D) = 71 B9 C2 FD 11 79 A3 CC FB 30 BF C6 5E 3B 18 87 A7 1C 52 BF 7F B4 61 42 BF C6 5E 3B 18 87 A7 1C 52 BF 7F B4 61 42 8A CB 49 F4 72 98 29 55 F8 04 FB 38 F5 87 65 98 9D D0 F0 F4 3A EC 12 02 43 F5 39 BF 40 2E 4F 0B 37 29 C6 DB A7 3B FC C7 FB 90 CF 6F 15 D3 34 75 EE 2A 4C 36 E4 39 F2 D1 4B 87 82 37 16 A3 84 E8 D8 2D 19 F3 A8 20 6E 66 C7 22 16 A3 84 E8 D8 2D 19 F3 A8 20 6E 66 C7 22 6D 2A F0 DA A1 E2 C1 54 29 E2 C5 A1 26 11 CA FC 4A E9 EA 32 95 78 41 31 9E 78 04 A8 B6 7C 0E E7 2B 32 87 E3 0A 84 67 F4 83 3F 53 C8 B1 BE EE 07 02 10 97 67 AF 0C DF 2B 20 AC E0 7E 42 7B 98 01 CB CE B8 13 52 8B 34 9A 4D A0 14 BA 6E 88 0E 2F F9 06 B5 1E 4A 00 D7 0E 0A 58 75 7D 39 2E B1 38 A0 4A 13 1C 3E 71 8C 78 CA F7 39 2E B1 38 A0 4A 13 1C 3E 71 8C 78 CA F7 46 06 FA F4 99 D8 52 A1 D7 70 12 40 1B 61 82 3D 8D F6 96 F1 C5 DB 1C E3 F8 9D DD 8A 2C 2C F5 62 EC BF FD C1 F0 77 58 0B FD 29 DE 23 D0 AF CD 46 90 A2 A1 D4 50 6D B2 52 D4 4A 2A EF 7D 4E 6E F8 63 41 BE D8 5F A1 A9 43 BF BC E1 54 C0 A0 33 CD 1B C6 84 2E 72 31 F7 E2 A7 91 3C DB 2D FD A7 84 CA 87 A2 3C 64 A4 04 82 4B 88 74 74 43 45 E1 48 FA BB 15 A6 D5 82 3F FF 2A BA C8 AF F8 E1 77 15 0D 5C 84 EB 40 C7 1E 52 16 CB EB 75 04 54 17 95 09 BF FD CA E8 C7 D1 93 F8 83 6B 50 26 A8 E6 23 00 AA EB 75 56 2D 24 62 CC 79 4E AA 92 B6 F6 CA BA 57 05 57 B3 53 32 60 4B 3B 20 D0 F6 57 31 52 49 EC B0 0B C0 97 D6 39 AC 16 F6 57 31 52 49 EC B0 0B C0 97 D6 39 AC 16 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
2092749761 同意后收到以下几个包
Mirai 22:04:40 : Packet received: UnknownEventPacket(id=02 10, identity=(1994701021->1994701021))
= 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 06 00 00 00 08 08 02 1A 02 08 44 2A 06 08 F9 D4 A5 EE 05
Mirai 22:04:45 : Packet received: UnknownEventPacket(id=02 10, identity=(1040400290->1994701021))
//5�������%�ԥ� (2对方正在输入...(
= 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 07 00 00 00 35 08 02 1A 03 08 95 02 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 1A 25 08 00 10 05 18 FE D4 A5 EE 05 20 01 28 08 32 15 E5 AF B9 E6 96 B9 E6 AD A3 E5 9C A8 E8 BE 93 E5 85 A5 2E 2E 2E 28 01
Mirai 22:04:45 : Packet received: FriendConversationInitialize(qq=1040400290)
Mirai 22:04:48 : Packet received: UnknownEventPacket(id=00 BC, identity=(2092749761->1994701021))
= 00 00 00 08 00 0A 00 04 01 00 00 00 01 00 00 0F 00 00 00 00 00 00 00 00 01 03 EB 00 02 0A 00
Mirai 22:04:48 : Packet received: UnknownEventPacket(id=00 D6, identity=(2092749761->1994701021))
= 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 00 BC 01 00 00 00 00 02 00 00
Mirai 22:04:48 : Packet received: UnknownEventPacket(id=02 02, identity=(1994701021->1994701021))
= 00 00 00 08 00 0A 00 04 01 00 00 00 02 00 00
Mirai 22:04:48 : Packet received: UnknownEventPacket(id=02 10, identity=(1994701021->1994701021))
//:�8����同意你的加好友请求"him188的老公(
= 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 07 00 00 00 3A 08 02 1A 03 08 E2 01 0A 38 08 01 10 C1 A7 F3 E5 07 1A 1B E5 90 8C E6 84 8F E4 BD A0 E7 9A 84 E5 8A A0 E5 A5 BD E5 8F 8B E8 AF B7 E6 B1 82 22 0F 68 69 6D 31 38 38 E7 9A 84 E8 80 81 E5 85 AC 28 01
Mirai 22:04:48 : Packet received: UnknownEventPacket(id=02 10, identity=(1994701021->1994701021))
= 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 06 00 00 00 08 08 02 1A 02 08 44 2A 06 08 81 D5 A5 EE 05
Mirai 22:04:48 : Packet received: UnknownPacket(01 2D)
body=01 01 00 00 00 01 7C BC D3 C1 00 00 00
Mirai 22:04:48 : UnknownPacket(01 2D) = 66 B9 2C A3 EA 31 AD 5B E4 52 37 25 3B 21 A9 FF EE 2E 80 AF 6F 9B C0 A7 37 25 3B 21 A9 FF EE 2E 80 AF 6F 9B C0 A7 6D FF BF 73 37 8A 34 4C D1 DF D3 ED 93 DA B3 32 AC 4D 11 D7 68 F0 93 A3 F5 BE 93 DA B3 32 AC 4D 11 D7 68 F0 93 A3 F5 BE CC 5F 8A DC 52 D7 2E 39 F6 D0 A6 FE E2 FE A9 1D C1 5E AC 2A 02 46 A0 90 23 6E 10 A6 48 B0 04 BC 06 F8 AF 42 87 DA 69 42 55 AA 48 B0 04 BC 06 F8 AF 42 87 DA 69 42 55 AA 29 3B C5 0C 5B 43 EE FA 30 EA 81 86 F3 8D FB 41 EA A3 23 59 27 49 03 B6 34 5C 4A CA DE 8E 3C 02 36 5F 48 C7 14 73 E4 D4 D8 C3 81 80 FC 49 9B 3C CC 4D D8 E9 07 84 56 40 F9 7E 80 D8 11 60 B1 FF F0 0E 5D 6E 4B 45 41 B4 81 54 EB B9 EE 98 D2 29 F3 05 BD 96 D3 E4 A6 42 98 CD C4 D1 5F 10 DE 62 EB E5 D3 E4 A6 42 98 CD C4 D1 5F 10 DE 62 EB E5 25 61 AA 54 A1 BE 14 78 F9 AC 2B F1 43 0B B5 51 2D 15 AA DE 97 B8 CC A3 2A 9B 8B AB 37 7C 45 57 D6 B9 BF 6C 4B 7B 66 AD 89 EB 90 42 0F 5F 63 A7 CC 06 4D 08 E0 5C 5D E3 9A AF 0D 19 C7 78 B5 30 6C 9D E2 A4 CA 3A DD 64 FC 78 A8 E1 59 1F 67 97 C6 B2 0B 73 EB 9A 2D 75 07 7E CE 82 3B EC CF 3A 9F 98 4F C0 BA 98 69 D7 65 87 EA 53 90 18 01 BD 8B AB EB 40 74 9C 03 C4 92 3B 9A F5 3A DD 51 84 EF 72 48 71 DC B4 AA D5 95 AB BC 4B 97 70 4D FD EE DE 37 BD 33 0C DF 64 C5 55 2E ED A6 98 6A 88 28 8B F3 24 8D 73 00 DE 9E FC 78 15 4A AC E2 3F AD 93 4C 2F 88 48 34 DA F3 F7 FC B7 E7 39 F6 33 3E 5C 88 48 34 DA F3 F7 FC B7 E7 39 F6 33 3E 5C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*/
\ No newline at end of file
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.event.internal package net.mamoe.mirai.event.internal
import kotlinx.coroutines.* import kotlinx.coroutines.CompletableJob
import kotlinx.coroutines.Job
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.EventDebugLogger
import net.mamoe.mirai.event.ListeningStatus import net.mamoe.mirai.event.ListeningStatus
import net.mamoe.mirai.event.Subscribable import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.network.session
import net.mamoe.mirai.utils.internal.inlinedRemoveIf
import net.mamoe.mirai.utils.io.logStacktrace import net.mamoe.mirai.utils.io.logStacktrace
import net.mamoe.mirai.utils.unsafeWeakRef
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
import kotlin.jvm.JvmField import kotlin.jvm.JvmField
...@@ -24,8 +24,6 @@ import kotlin.reflect.KFunction ...@@ -24,8 +24,6 @@ import kotlin.reflect.KFunction
*/ */
var EventDisabled = false var EventDisabled = false
// TODO: 2019/11/29 修改监听为 lock-free 模式
/** /**
* 监听和广播实现. * 监听和广播实现.
* 它会首先检查这个事件是否正在被广播 * 它会首先检查这个事件是否正在被广播
...@@ -34,38 +32,9 @@ var EventDisabled = false ...@@ -34,38 +32,9 @@ var EventDisabled = false
* *
* @author Him188moe * @author Him188moe
*/ // inline to avoid a Continuation creation */ // inline to avoid a Continuation creation
internal suspend inline fun <L : Listener<E>, E : Subscribable> KClass<E>.subscribeInternal(listener: L): L = with(this.listeners()) { internal suspend inline fun <L : Listener<E>, E : Subscribable> KClass<E>.subscribeInternal(listener: L): L {
if (mainMutex.tryLock(listener)) {//能锁则代表这个事件目前没有正在广播. this.listeners().addLast(listener)
try { return listener
add(listener)//直接修改主监听者列表
EventDebugLogger.debug("Added a listener to ${this@subscribeInternal.simpleName}")
} finally {
mainMutex.unlock(listener)
}
return listener
}
//不能锁住, 则这个事件正在广播, 那么要将新的监听者放入缓存
cacheMutex.withLock {
cache.add(listener)
EventDebugLogger.debug("Added a listener to cache of ${this@subscribeInternal.simpleName}")
}
GlobalScope.launch {
//启动协程并等待正在进行的广播结束, 然后将缓存转移到主监听者列表
//启动后的协程马上就会因为锁而被挂起
mainMutex.withLock(listener) {
cacheMutex.withLock {
if (cache.size != 0) {
addAll(cache)
cache.clear()
EventDebugLogger.debug("Cache of ${this@subscribeInternal.simpleName} is now transferred to main")
}
}
}
}
return@with listener
} }
/** /**
...@@ -77,8 +46,6 @@ internal suspend inline fun <L : Listener<E>, E : Subscribable> KClass<E>.subscr ...@@ -77,8 +46,6 @@ internal suspend inline fun <L : Listener<E>, E : Subscribable> KClass<E>.subscr
* @author Him188moe * @author Him188moe
*/ */
sealed class Listener<in E : Subscribable> : CompletableJob { sealed class Listener<in E : Subscribable> : CompletableJob {
@JvmField
internal val lock = Mutex()
abstract suspend fun onEvent(event: E): ListeningStatus abstract suspend fun onEvent(event: E): ListeningStatus
} }
...@@ -113,7 +80,7 @@ internal class Handler<in E : Subscribable> ...@@ -113,7 +80,7 @@ internal class Handler<in E : Subscribable>
@Suppress("FunctionName") @Suppress("FunctionName")
internal suspend inline fun <E : Subscribable> HandlerWithSession( internal suspend inline fun <E : Subscribable> HandlerWithSession(
bot: Bot, bot: Bot,
noinline handler: suspend BotSession.(E) -> ListeningStatus noinline handler: suspend Bot.(E) -> ListeningStatus
): HandlerWithSession<E> { ): HandlerWithSession<E> {
return HandlerWithSession(bot, coroutineContext[Job], coroutineContext, handler) return HandlerWithSession(bot, coroutineContext[Job], coroutineContext, handler)
} }
...@@ -125,10 +92,10 @@ internal suspend inline fun <E : Subscribable> HandlerWithSession( ...@@ -125,10 +92,10 @@ internal suspend inline fun <E : Subscribable> HandlerWithSession(
*/ */
@PublishedApi @PublishedApi
internal class HandlerWithSession<E : Subscribable> @PublishedApi internal constructor( internal class HandlerWithSession<E : Subscribable> @PublishedApi internal constructor(
@JvmField val bot: Bot, bot: Bot,
parentJob: Job?, private val context: CoroutineContext, @JvmField val handler: suspend BotSession.(E) -> ListeningStatus parentJob: Job?, private val context: CoroutineContext, @JvmField val handler: suspend Bot.(E) -> ListeningStatus
) : ) : Listener<E>(), CompletableJob by Job(parentJob) {
Listener<E>(), CompletableJob by Job(parentJob) { val bot: Bot by bot.unsafeWeakRef()
override suspend fun onEvent(event: E): ListeningStatus { override suspend fun onEvent(event: E): ListeningStatus {
if (isCompleted || isCancelled) return ListeningStatus.STOPPED if (isCompleted || isCancelled) return ListeningStatus.STOPPED
...@@ -137,10 +104,11 @@ internal class HandlerWithSession<E : Subscribable> @PublishedApi internal const ...@@ -137,10 +104,11 @@ internal class HandlerWithSession<E : Subscribable> @PublishedApi internal const
if (event !is BotEvent || event.bot !== bot) return ListeningStatus.LISTENING if (event !is BotEvent || event.bot !== bot) return ListeningStatus.LISTENING
return try { return try {
withContext(context) { bot.session.handler(event) }.also { if (it == ListeningStatus.STOPPED) complete() } withContext(context) { bot.handler(event) }.also { if (it == ListeningStatus.STOPPED) complete() }
} catch (e: Throwable) { } catch (e: Throwable) {
e.logStacktrace() e.logStacktrace()
//completeExceptionally(e) //completeExceptionally(e)
complete()
ListeningStatus.STOPPED ListeningStatus.STOPPED
} }
} }
...@@ -151,24 +119,7 @@ internal class HandlerWithSession<E : Subscribable> @PublishedApi internal const ...@@ -151,24 +119,7 @@ internal class HandlerWithSession<E : Subscribable> @PublishedApi internal const
*/ */
internal suspend fun <E : Subscribable> KClass<out E>.listeners(): EventListeners<E> = EventListenerManger.get(this) internal suspend fun <E : Subscribable> KClass<out E>.listeners(): EventListeners<E> = EventListenerManger.get(this)
internal class EventListeners<E : Subscribable> : MutableList<Listener<E>> by mutableListOf() { internal class EventListeners<E : Subscribable> : LockFreeLinkedList<Listener<E>>() {
/**
* 主监听者列表.
* 广播事件时使用这个锁.
*/
@JvmField
val mainMutex = Mutex()
/**
* 缓存(监听)事件时使用的锁
*/
@JvmField
val cacheMutex = Mutex()
/**
* 等待加入到主 list 的监听者. 务必使用 [cacheMutex]
*/
@JvmField
val cache: MutableList<Listener<E>> = mutableListOf()
init { init {
this::class.members.filterIsInstance<KFunction<*>>().forEach { this::class.members.filterIsInstance<KFunction<*>>().forEach {
if (it.name == "add") { if (it.name == "add") {
...@@ -198,51 +149,26 @@ internal object EventListenerManger { ...@@ -198,51 +149,26 @@ internal object EventListenerManger {
} }
internal suspend fun <E : Subscribable> E.broadcastInternal(): E { // inline: NO extra Continuation
internal suspend inline fun <E : Subscribable> E.broadcastInternal(): E {
if (EventDisabled) return this if (EventDisabled) return this
callListeners(this::class.listeners()) callAndRemoveIfRequired(this::class.listeners())
applySuperListeners(this::class) { callListeners(it) }
return this
}
private suspend inline fun <E : Subscribable> E.callListeners(listeners: EventListeners<in E>) { this::class.supertypes.forEach { superType ->
//自己持有, 则是在一个事件中 if (Subscribable::class.isInstance(superType)) {
if (listeners.mainMutex.holdsLock(listeners)) { // the super type is a child of Subscribable, then we can cast.
callAndRemoveIfRequired(listeners) @Suppress("UNCHECKED_CAST")
} else { callAndRemoveIfRequired((superType.classifier as KClass<out Subscribable>).listeners())
while (!listeners.mainMutex.tryLock(listeners)) {
delay(10)
}
try {
callAndRemoveIfRequired(listeners)
} finally {
listeners.mainMutex.unlock(listeners)
} }
} }
return this
} }
private suspend inline fun <E : Subscribable> E.callAndRemoveIfRequired(listeners: EventListeners<in E>) = listeners.inlinedRemoveIf { private suspend inline fun <E : Subscribable> E.callAndRemoveIfRequired(listeners: EventListeners<E>) {
if (it.lock.tryLock()) { listeners.forEach {
try { if (it.onEvent(this) == ListeningStatus.STOPPED) {
it.onEvent(this) == ListeningStatus.STOPPED listeners.remove(it) // atomic remove
} finally {
it.lock.unlock()
} }
} else false }
}
/**
* apply [block] to all the [EventListeners] in [clazz]'s superclasses
*/
private tailrec suspend fun <E : Subscribable> applySuperListeners(
clazz: KClass<out E>,
block: suspend (EventListeners<in E>) -> Unit
) {
val superEventClass =
clazz.supertypes.map { it.classifier }.filterIsInstance<KClass<out Subscribable>>().firstOrNull() ?: return
@Suppress("UNCHECKED_CAST")
block(superEventClass.listeners() as EventListeners<in E>)
applySuperListeners(superEventClass, block)
} }
\ No newline at end of file
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
package net.mamoe.mirai.message
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.utils.MiraiInternalAPI
class FriendMessage(
bot: Bot,
/**
* 是否是在这次登录之前的消息, 即消息记录
*/
val previous: Boolean,
override val sender: QQ,
override val message: MessageChain
) : MessagePacket<QQ, QQ>(bot), BroadcastControllable {
/**
* 是否应被自动广播. 此为内部 API
*/
@MiraiInternalAPI
override val shouldBroadcast: Boolean
get() = !previous
override val subject: QQ get() = sender
}
package net.mamoe.mirai.message
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.message.data.At
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.utils.unsafeWeakRef
@Suppress("unused", "NOTHING_TO_INLINE")
class GroupMessage(
bot: Bot,
group: Group,
val senderName: String,
/**
* 发送方权限.
*/
val permission: MemberPermission,
sender: Member,
override val message: MessageChain
) : MessagePacket<Member, Group>(bot) {
val group: Group by group.unsafeWeakRef()
override val sender: Member by sender.unsafeWeakRef()
/*
01 00 09 01 00 06 66 61 69 6C 65 64 19 00 45 01 00 42 AA 02 3F 08 06 50 02 60 00 68 00 88 01 00 9A 01 31 08 0A 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 02 B0 03 00 C0 03 00 D0 03 00 E8 03 02 8A 04 04 08 02 08 01 90 04 80 C8 10 0E 00 0E 01 00 04 00 00 08 E4 07 00 04 00 00 00 01 12 00 1E 02 00 09 E9 85 B1 E9 87 8E E6 98 9F 03 00 01 02 05 00 04 00 00 00 03 08 00 04 00 00 00 04
*/
override val subject: Group get() = group
inline fun At.member(): Member = group.getMember(this.target)
inline fun Long.member(): Member = group.getMember(this)
override fun toString(): String =
"GroupMessage(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)"
}
\ No newline at end of file
@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.message
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.String
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.getGroup import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.* import net.mamoe.mirai.network.data.EventPacket
import net.mamoe.mirai.message.internal.readMessageChain import net.mamoe.mirai.network.data.ImageLink
import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion
import net.mamoe.mirai.network.protocol.timpc.packet.action.ImageLink
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
import net.mamoe.mirai.utils.io.printTLVMap
import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.readTLVMap
import net.mamoe.mirai.utils.io.readUShortLVByteArray
import net.mamoe.mirai.withSession
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
/** /**
* 平台相关扩展 * 平台相关扩展
*/ */
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
expect abstract class MessagePacket<TSender : QQ, TSubject : Contact>() : MessagePacketBase<TSender, TSubject> expect abstract class MessagePacket<TSender : QQ, TSubject : Contact>(bot: Bot) : MessagePacketBase<TSender, TSubject>
@MiraiInternalAPI @MiraiInternalAPI
abstract class MessagePacketBase<TSender : QQ, TSubject : Contact> : EventPacket, BotEvent() { abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) : EventPacket, BotEvent() {
internal lateinit var botVar: Bot override val bot: Bot by _bot.unsafeWeakRef()
override val bot: Bot get() = botVar
/** /**
* 消息事件主体. * 消息事件主体.
...@@ -86,7 +73,7 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact> : EventPacket ...@@ -86,7 +73,7 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact> : EventPacket
// endregion // endregion
// region Image download // region Image download
suspend inline fun Image.getLink(): ImageLink = bot.withSession { getLink() } suspend inline fun Image.getLink(): ImageLink = with(bot) { getLink() }
suspend inline fun Image.downloadAsByteArray(): ByteArray = getLink().downloadAsByteArray() suspend inline fun Image.downloadAsByteArray(): ByteArray = getLink().downloadAsByteArray()
suspend inline fun Image.download(): ByteReadPacket = getLink().download() suspend inline fun Image.download(): ByteReadPacket = getLink().download()
...@@ -94,137 +81,11 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact> : EventPacket ...@@ -94,137 +81,11 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact> : EventPacket
fun At.qq(): QQ = bot.getQQ(this.target) fun At.qq(): QQ = bot.getQQ(this.target)
fun Int.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0).toUInt()) fun Int.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0).toLong())
fun Long.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0)) fun Long.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0))
fun UInt.qq(): QQ = bot.getQQ(this)
suspend inline fun Int.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0).toUInt()) suspend inline fun Int.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0).toLong())
suspend inline fun Long.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0)) suspend inline fun Long.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0))
suspend inline fun UInt.group(): Group = bot.getGroup(GroupId(this))
suspend inline fun GroupId.group(): Group = bot.getGroup(this) suspend inline fun GroupId.group(): Group = bot.getGroup(this)
suspend inline fun GroupInternalId.group(): Group = bot.getGroup(this) suspend inline fun GroupInternalId.group(): Group = bot.getGroup(this)
} }
\ No newline at end of file
// region group message
@Suppress("unused", "NOTHING_TO_INLINE")
data class GroupMessage(
val group: Group,
val senderName: String,
/**
* 发送方权限.
*/
val permission: MemberPermission,
override val sender: Member,
override val message: MessageChain
) : MessagePacket<Member, Group>() {
/*
01 00 09 01 00 06 66 61 69 6C 65 64 19 00 45 01 00 42 AA 02 3F 08 06 50 02 60 00 68 00 88 01 00 9A 01 31 08 0A 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 02 B0 03 00 C0 03 00 D0 03 00 E8 03 02 8A 04 04 08 02 08 01 90 04 80 C8 10 0E 00 0E 01 00 04 00 00 08 E4 07 00 04 00 00 00 01 12 00 1E 02 00 09 E9 85 B1 E9 87 8E E6 98 9F 03 00 01 02 05 00 04 00 00 00 03 08 00 04 00 00 00 04
*/
override val subject: Group get() = group
inline fun At.member(): Member = group.getMember(this.target)
inline fun UInt.member(): Member = group.getMember(this)
inline fun Long.member(): Member = group.getMember(this.toUInt())
override fun toString(): String =
"GroupMessage(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)"
}
@PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)")
internal object GroupMessageEventParserAndHandler : KnownEventParserAndHandler<GroupMessage>(0x0052u) {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): GroupMessage {
discardExact(31)
val groupNumber = readUInt()
discardExact(1)
val qq = readUInt()
discardExact(48)
readUShortLVByteArray()
discardExact(2)//2个0x00
//debugPrintIfFail {
val message = readMessageChain()
var senderPermission: MemberPermission = MemberPermission.MEMBER
var senderName = ""
val map = readTLVMap(true)
if (map.containsKey(18u)) {
map.getValue(18u).read {
val tlv = readTLVMap(true)
senderPermission = when (tlv.takeIf { it.containsKey(0x04u) }?.get(0x04u)?.getOrNull(3)?.toUInt()) {
null -> MemberPermission.MEMBER
0x08u -> MemberPermission.OWNER
0x10u -> MemberPermission.ADMINISTRATOR
else -> {
tlv.printTLVMap("TLV(tag=18) Map")
MiraiLogger.warning("Could not determine member permission, default permission MEMBER is being used")
MemberPermission.MEMBER
}
}
senderName = when {
tlv.containsKey(0x01u) -> String(tlv.getValue(0x01u))//这个人的qq昵称
tlv.containsKey(0x02u) -> String(tlv.getValue(0x02u))//这个人的群名片
else -> {
tlv.printTLVMap("TLV(tag=18) Map")
MiraiLogger.warning("Could not determine senderName")
"null"
}
}
}
}
val group = bot.getGroup(groupNumber)
return GroupMessage(group, senderName, senderPermission, group.getMember(qq), message).apply { this.botVar = bot }
// }
}
}
// endregion
// region friend message
data class FriendMessage(
/**
* 是否是在这次登录之前的消息, 即消息记录
*/
val previous: Boolean,
override val sender: QQ,
override val message: MessageChain
) : MessagePacket<QQ, QQ>(), BroadcastControllable {
/**
* 是否应被自动广播. 此为内部 API
*/
@MiraiInternalAPI
override val shouldBroadcast: Boolean
get() = !previous
override val subject: QQ get() = sender
}
@Suppress("unused")
@PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)")
internal object FriendMessageEventParserAndHandler : KnownEventParserAndHandler<FriendMessage>(0x00A6u) {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): FriendMessage {
discardExact(2)
val l1 = readShort()
discardExact(1)//0x00
val previous = readByte().toInt() == 0x08
discardExact(l1.toInt() - 2)
//java.io.EOFException: Only 49 bytes were discarded of 69 requested
//抖动窗口消息
discardExact(69)
readUShortLVByteArray()//font
discardExact(2)//2个0x00
val message = readMessageChain()
return FriendMessage(
previous = previous,
sender = bot.getQQ(identity.from),
message = message
).apply { this.botVar = bot }
}
}
// endregion
\ No newline at end of file
@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. 因为手机端只会显示这个文字
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") package net.mamoe.mirai.message.data
package net.mamoe.mirai.message
import kotlin.jvm.JvmStatic import kotlin.jvm.JvmStatic
/**
* QQ 自带表情
*/
inline class Face(val id: FaceId) : Message {
override val stringValue: String get() = "[face${id.value}]"
override fun toString(): String = stringValue
companion object Key : Message.Key<Face>
}
/** /**
* @author LamGC * @author LamGC
*/ */
@Suppress("SpellCheckingInspection", "unused") @Suppress("SpellCheckingInspection", "unused")
inline class FaceId(inline val value: UByte) { @UseExperimental(ExperimentalUnsignedTypes::class)
inline class FaceId constructor(inline val value: UByte) {
companion object { companion object {
@JvmStatic @JvmStatic
val unknown: FaceId = FaceId(0xffu) val unknown: FaceId = FaceId(0xffu)
......
@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.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.sendMessage import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.network.protocol.timpc.packet.action.FriendImagePacket
import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.ExternalImage
...@@ -30,7 +29,8 @@ inline class ImageId0x06(override inline val value: String) : ImageId { ...@@ -30,7 +29,8 @@ inline class ImageId0x06(override inline val value: String) : ImageId {
/** /**
* 一般是群的图片的 id. * 一般是群的图片的 id.
*/ */
class ImageId0x03 constructor(override inline val value: String, inline val uniqueId: UInt, inline val height: Int, inline val width: Int) : ImageId { class ImageId0x03 constructor(override inline val value: String, inline val uniqueId: UInt, inline val height: Int, inline val width: Int) :
ImageId {
override fun toString(): String = "ImageId(value=$value, uniqueId=${uniqueId}, height=$height, width=$width)" override fun toString(): String = "ImageId(value=$value, uniqueId=${uniqueId}, height=$height, width=$width)"
val md5: ByteArray val md5: ByteArray
...@@ -46,7 +46,8 @@ class ImageId0x03 constructor(override inline val value: String, inline val uniq ...@@ -46,7 +46,8 @@ class ImageId0x03 constructor(override inline val value: String, inline val uniq
inline fun ImageId(value: String): ImageId = ImageId0x06(value) inline fun ImageId(value: String): ImageId = ImageId0x06(value)
@Suppress("FunctionName", "NOTHING_TO_INLINE") @Suppress("FunctionName", "NOTHING_TO_INLINE")
inline fun ImageId(value: String, uniqueId: UInt, height: Int, width: Int): ImageId = ImageId0x03(value, uniqueId, height, width) inline fun ImageId(value: String, uniqueId: UInt, height: Int, width: Int): ImageId =
ImageId0x03(value, uniqueId, height, width)
/** /**
...@@ -65,6 +66,7 @@ fun ImageId.checkLength() = check(value.length == 37 || value.length == 42) { "I ...@@ -65,6 +66,7 @@ fun ImageId.checkLength() = check(value.length == 37 || value.length == 42) { "I
fun ImageId.requireLength() = require(value.length == 37 || value.length == 42) { "Illegal ImageId length" } fun ImageId.requireLength() = require(value.length == 37 || value.length == 42) { "Illegal ImageId length" }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun ImageId.image(): Image = Image(this) inline fun ImageId.image(): Image =
Image(this)
suspend inline fun ImageId.sendTo(contact: Contact) = contact.sendMessage(this.image()) suspend inline fun ImageId.sendTo(contact: Contact) = contact.sendMessage(this.image())
@file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE") @file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.message package net.mamoe.mirai.message.data
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.sendMessage import net.mamoe.mirai.contact.sendMessage
......
package net.mamoe.mirai.message package net.mamoe.mirai.message.data
import net.mamoe.mirai.message.data.NullMessageChain.toString
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract import kotlin.contracts.contract
...@@ -75,7 +76,8 @@ fun MessageChain(vararg messages: Message): MessageChain = ...@@ -75,7 +76,8 @@ fun MessageChain(vararg messages: Message): MessageChain =
* 构造 [MessageChain] * 构造 [MessageChain]
*/ */
@Suppress("FunctionName") @Suppress("FunctionName")
fun MessageChain(messages: Iterable<Message>): MessageChain = MessageChainImpl(messages.toMutableList()) fun MessageChain(messages: Iterable<Message>): MessageChain =
MessageChainImpl(messages.toMutableList())
/** /**
* 构造单元素的不可修改的 [MessageChain]. 内部类实现为 [SingleMessageChain] * 构造单元素的不可修改的 [MessageChain]. 内部类实现为 [SingleMessageChain]
...@@ -106,13 +108,16 @@ fun SingleMessageChain(delegate: Message): MessageChain { ...@@ -106,13 +108,16 @@ fun SingleMessageChain(delegate: Message): MessageChain {
* 否则将调用 [MessageChain] 构造一个 [MessageChainImpl] * 否则将调用 [MessageChain] 构造一个 [MessageChainImpl]
*/ */
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun Message.chain(): MessageChain = if (this is MessageChain) this else MessageChain(this) inline fun Message.chain(): MessageChain = if (this is MessageChain) this else MessageChain(
this
)
/** /**
* 构造 [MessageChain] * 构造 [MessageChain]
*/ */
@Suppress("unused", "NOTHING_TO_INLINE") @Suppress("unused", "NOTHING_TO_INLINE")
inline fun List<Message>.messageChain(): MessageChain = MessageChain(this) inline fun List<Message>.messageChain(): MessageChain =
MessageChain(this)
/** /**
...@@ -230,51 +235,45 @@ class EmptyMessageChain : MessageChain { ...@@ -230,51 +235,45 @@ class EmptyMessageChain : MessageChain {
* Null 的 [MessageChain]. * Null 的 [MessageChain].
* 它不包含任何元素, 也没有创建任何 list. * 它不包含任何元素, 也没有创建任何 list.
* *
* - 所有 get 方法均抛出 [IndexOutOfBoundsException] * 除 [toString] 外, 其他方法均 [error]
* - 所有 add 方法均抛出 [UnsupportedOperationException]
* - 其他判断类方法均 false 或 -1
*/ */
object NullMessageChain : MessageChain { object NullMessageChain : MessageChain {
override fun subList(fromIndex: Int, toIndex: Int): MutableList<Message> = unsupported() override fun subList(fromIndex: Int, toIndex: Int): MutableList<Message> = error("accessing NullMessageChain")
override val stringValue: String override val stringValue: String get() = "null"
get() = ""
override fun toString(): String = stringValue override fun toString(): String = "null"
override fun contains(sub: String): Boolean = false override fun contains(sub: String): Boolean = error("accessing NullMessageChain")
override fun contains(element: Message): Boolean = false override fun contains(element: Message): Boolean = error("accessing NullMessageChain")
override fun followedBy(tail: Message): MessageChain = override fun followedBy(tail: Message): MessageChain = error("accessing NullMessageChain")
MessageChainImpl(tail)
override val size: Int = 0
override fun containsAll(elements: Collection<Message>): Boolean = false
override fun get(index: Int): Message = throw IndexOutOfBoundsException()
override fun indexOf(element: Message): Int = -1
override fun isEmpty(): Boolean = true
override fun iterator(): MutableIterator<Message> =
EmptyMutableIterator()
override fun lastIndexOf(element: Message): Int = -1
override fun add(element: Message): Boolean = unsupported()
override fun add(index: Int, element: Message) = throw IndexOutOfBoundsException(index.toString())
override fun addAll(index: Int, elements: Collection<Message>): Boolean =
throw IndexOutOfBoundsException(index.toString())
override fun addAll(elements: Collection<Message>): Boolean = unsupported()
override fun clear() {}
override fun listIterator(): MutableListIterator<Message> =
EmptyMutableListIterator()
override fun listIterator(index: Int): MutableListIterator<Message> = override val size: Int get() = error("accessing NullMessageChain")
throw IndexOutOfBoundsException(index.toString()) override fun containsAll(elements: Collection<Message>): Boolean = error("accessing NullMessageChain")
override fun get(index: Int): Message = error("accessing NullMessageChain")
override fun remove(element: Message): Boolean = false override fun indexOf(element: Message): Int = error("accessing NullMessageChain")
override fun removeAll(elements: Collection<Message>): Boolean = false override fun isEmpty(): Boolean = error("accessing NullMessageChain")
override fun removeAt(index: Int): Message = throw IndexOutOfBoundsException(index.toString()) override fun iterator(): MutableIterator<Message> = error("accessing NullMessageChain")
override fun retainAll(elements: Collection<Message>): Boolean = false
override fun set(index: Int, element: Message): Message = throw IndexOutOfBoundsException(index.toString()) override fun lastIndexOf(element: Message): Int = error("accessing NullMessageChain")
private fun unsupported(): Nothing = throw UnsupportedOperationException() override fun add(element: Message): Boolean = error("accessing NullMessageChain")
override fun add(index: Int, element: Message) = error("accessing NullMessageChain")
override fun addAll(index: Int, elements: Collection<Message>): Boolean = error("accessing NullMessageChain")
override fun addAll(elements: Collection<Message>): Boolean = error("accessing NullMessageChain")
override fun clear() {
error("accessing NullMessageChain")
}
override fun listIterator(): MutableListIterator<Message> = error("accessing NullMessageChain")
override fun listIterator(index: Int): MutableListIterator<Message> = error("accessing NullMessageChain")
override fun remove(element: Message): Boolean = error("accessing NullMessageChain")
override fun removeAll(elements: Collection<Message>): Boolean = error("accessing NullMessageChain")
override fun removeAt(index: Int): Message = error("accessing NullMessageChain")
override fun retainAll(elements: Collection<Message>): Boolean = error("accessing NullMessageChain")
override fun set(index: Int, element: Message): Message = error("accessing NullMessageChain")
} }
/** /**
......
package net.mamoe.mirai.message package net.mamoe.mirai.message.data
inline class PlainText(override val stringValue: String) : Message { inline class PlainText(override val stringValue: String) : Message {
......
@file:Suppress("MemberVisibilityCanBePrivate") @file:Suppress("MemberVisibilityCanBePrivate")
package net.mamoe.mirai.message package net.mamoe.mirai.message.data
/** /**
* XML 消息, 如分享, 卡片等. * XML 消息, 如分享, 卡片等.
* *
* @see buildXMLMessage * @see buildXMLMessage
*/ */
inline class XMLMessage(override val stringValue: String) : Message, SingleOnly { inline class XMLMessage(override val stringValue: String) : Message,
SingleOnly {
override fun followedBy(tail: Message): Nothing = error("XMLMessage Message cannot be followed") override fun followedBy(tail: Message): Nothing = error("XMLMessage Message cannot be followed")
override fun toString(): String = stringValue override fun toString(): String = stringValue
} }
...@@ -16,7 +17,8 @@ inline class XMLMessage(override val stringValue: String) : Message, SingleOnly ...@@ -16,7 +17,8 @@ inline class XMLMessage(override val stringValue: String) : Message, SingleOnly
* 构造一条 XML 消息 * 构造一条 XML 消息
*/ */
@XMLDsl @XMLDsl
inline fun buildXMLMessage(block: @XMLDsl XMLMessageBuilder.() -> Unit): XMLMessage = XMLMessage(XMLMessageBuilder().apply(block).text) inline fun buildXMLMessage(block: @XMLDsl XMLMessageBuilder.() -> Unit): XMLMessage =
XMLMessage(XMLMessageBuilder().apply(block).text)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
@XMLDsl @XMLDsl
......
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
package net.mamoe.mirai.message.internal package net.mamoe.mirai.message.internal
import kotlinx.io.core.* import kotlinx.io.core.*
import net.mamoe.mirai.message.* import net.mamoe.mirai.message.MessageType
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.unzip import net.mamoe.mirai.utils.unzip
...@@ -27,7 +28,7 @@ internal fun IoBuffer.parsePlainTextOrAt(): Message { ...@@ -27,7 +28,7 @@ internal fun IoBuffer.parsePlainTextOrAt(): Message {
PlainText(msg) PlainText(msg)
} else { } else {
discardExact(10) discardExact(10)
At(readUInt()) At(readQQ())
} }
} }
...@@ -38,13 +39,13 @@ internal fun IoBuffer.parseLongText0x19(): PlainText { ...@@ -38,13 +39,13 @@ internal fun IoBuffer.parseLongText0x19(): PlainText {
//AA 02 33 50 00 60 00 68 00 9A 01 2A 08 09 20 CB 50 78 80 80 04 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 D3 02 A0 03 10 B0 03 00 C0 03 AF 9C 01 D0 03 00 E8 03 00 //AA 02 33 50 00 60 00 68 00 9A 01 2A 08 09 20 CB 50 78 80 80 04 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 D3 02 A0 03 10 B0 03 00 C0 03 AF 9C 01 D0 03 00 E8 03 00
//AA 02 30 50 00 60 00 68 00 9A 01 27 08 0A 78 A7 C0 04 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 00 B0 03 00 C0 03 00 D0 03 00 E8 03 00 //AA 02 30 50 00 60 00 68 00 9A 01 27 08 0A 78 A7 C0 04 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 00 B0 03 00 C0 03 00 D0 03 00 E8 03 00
// 应该是手机发送时的字体或气泡之类的 // 应该是手机发送时的字体或气泡之类的
// println("parseLongText0x19.raw=${raw.toUHexString()}") // println("parseLongText0x19.raw=${raw.toHexString()}")
return PlainText("") return PlainText("")
} }
internal fun IoBuffer.parseMessageImage0x06(): Image { internal fun IoBuffer.parseMessageImage0x06(): Image {
discardExact(1) discardExact(1)
//MiraiLogger.debug(this.toUHexString()) //MiraiLogger.debug(this.toHexString())
val filenameLength = readShort() val filenameLength = readShort()
discardExact(filenameLength.toInt()) discardExact(filenameLength.toInt())
...@@ -199,7 +200,7 @@ internal fun ByteReadPacket.readMessage(): Message? { ...@@ -199,7 +200,7 @@ internal fun ByteReadPacket.readMessage(): Message? {
} }
} }
internal fun ByteReadPacket.readMessageChain(): MessageChain { fun ByteReadPacket.readMessageChain(): MessageChain {
val chain = MessageChain() val chain = MessageChain()
do { do {
if (this.remaining == 0L) { if (this.remaining == 0L) {
...@@ -209,7 +210,7 @@ internal fun ByteReadPacket.readMessageChain(): MessageChain { ...@@ -209,7 +210,7 @@ internal fun ByteReadPacket.readMessageChain(): MessageChain {
return chain return chain
} }
internal fun MessageChain.toPacket(): ByteReadPacket = buildPacket { fun MessageChain.toPacket(): ByteReadPacket = buildPacket {
this@toPacket.forEach { message -> this@toPacket.forEach { message ->
writePacket(with(message) { writePacket(with(message) {
when (this) { when (this) {
...@@ -244,7 +245,7 @@ internal fun MessageChain.toPacket(): ByteReadPacket = buildPacket { ...@@ -244,7 +245,7 @@ internal fun MessageChain.toPacket(): ByteReadPacket = buildPacket {
writeShortLVString(stringValue) // 这个应该是 "@群名", 手机上面会显示这个消息, 电脑会显示下面那个 writeShortLVString(stringValue) // 这个应该是 "@群名", 手机上面会显示这个消息, 电脑会显示下面那个
// 06 00 0D 00 01 00 00 00 08 00 76 E4 B8 DD 00 00 // 06 00 0D 00 01 00 00 00 08 00 76 E4 B8 DD 00 00
writeHex("06 00 0D 00 01 00 00 00 08 00") writeHex("06 00 0D 00 01 00 00 00 08 00")
writeUInt(target) writeQQ(target)
writeZero(2) writeZero(2)
} }
} }
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network package net.mamoe.mirai.network
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableJob import kotlinx.coroutines.CompletableJob
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.timpc.handler.DataPacketSocketAdapter
import net.mamoe.mirai.network.protocol.timpc.handler.TemporaryPacketHandler
import net.mamoe.mirai.network.protocol.timpc.packet.OutgoingPacket
import net.mamoe.mirai.network.protocol.timpc.packet.Packet
import net.mamoe.mirai.network.protocol.timpc.packet.login.HeartbeatPacket
import net.mamoe.mirai.network.protocol.timpc.packet.login.RequestSKeyPacket
import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.PlatformDatagramChannel import net.mamoe.mirai.utils.io.PlatformDatagramChannel
/** /**
...@@ -33,29 +27,41 @@ import net.mamoe.mirai.utils.io.PlatformDatagramChannel ...@@ -33,29 +27,41 @@ import net.mamoe.mirai.utils.io.PlatformDatagramChannel
* A BotNetworkHandler is used to connect with Tencent servers. * A BotNetworkHandler is used to connect with Tencent servers.
*/ */
@Suppress("PropertyName") @Suppress("PropertyName")
interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : CoroutineScope { abstract class BotNetworkHandler : CoroutineScope {
val socket: Socket abstract val bot: Bot
val bot: Bot
val supervisor: CompletableJob
val session: BotSession abstract val supervisor: CompletableJob
/** /**
* 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回登录结果 * 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回登录结果
* 本函数将挂起直到登录成功. * 本函数将挂起直到登录成功.
*/ */
suspend fun login(): LoginResult abstract suspend fun login()
/** /**
* 等待直到与服务器断开连接. 若未连接则立即返回 * 等待直到与服务器断开连接. 若未连接则立即返回
*/ */
suspend fun awaitDisconnection() abstract suspend fun awaitDisconnection()
/** /**
* 关闭网络接口, 停止所有有关协程和任务 * 关闭网络接口, 停止所有有关协程和任务
*/ */
fun close(cause: Throwable? = null) { open fun close(cause: Throwable? = null) {
supervisor.cancel(CancellationException("handler closed", cause)) supervisor.cancel(CancellationException("handler closed", cause))
} }
/*
@PublishedApi
internal abstract fun CoroutineScope.QQ(bot: Bot, id: Long, coroutineContext: CoroutineContext): QQ
@PublishedApi
internal abstract fun CoroutineScope.Group(bot: Bot, groupId: GroupId, info: RawGroupInfo, context: CoroutineContext): Group
@PublishedApi
internal abstract fun Group.Member(delegate: QQ, permission: MemberPermission, coroutineContext: CoroutineContext): Member
*/
} }
\ No newline at end of file
@file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "NOTHING_TO_INLINE")
package net.mamoe.mirai.network
import kotlinx.coroutines.*
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.*
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.GroupId
import net.mamoe.mirai.contact.GroupInternalId
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.Image
import net.mamoe.mirai.message.ImageId0x03
import net.mamoe.mirai.message.ImageId0x06
import net.mamoe.mirai.network.protocol.timpc.TIMBotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.handler.DataPacketSocketAdapter
import net.mamoe.mirai.network.protocol.timpc.handler.TemporaryPacketHandler
import net.mamoe.mirai.network.protocol.timpc.packet.OutgoingPacket
import net.mamoe.mirai.network.protocol.timpc.packet.Packet
import net.mamoe.mirai.network.protocol.timpc.packet.SessionKey
import net.mamoe.mirai.network.protocol.timpc.packet.action.*
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.assertUnreachable
import net.mamoe.mirai.utils.getGTK
import net.mamoe.mirai.utils.internal.PositiveNumbers
import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
import net.mamoe.mirai.utils.secondsToMillis
import kotlin.coroutines.coroutineContext
/**
* 构造 [BotSession] 的捷径
*/
@Suppress("FunctionName", "NOTHING_TO_INLINE")
internal inline fun TIMBotNetworkHandler.BotSession(sessionKey: SessionKey): BotSession = BotSession(bot, sessionKey)
/**
* 登录会话. 当登录完成后, 客户端会拿到 sessionKey.
* 此时建立 session, 然后开始处理事务.
*
* 本类中含有各平台相关扩展函数等.
*
* @author Him188moe
*/
@UseExperimental(MiraiInternalAPI::class)
expect class BotSession internal constructor(
bot: Bot,
sessionKey: SessionKey
) : BotSessionBase
/**
* [BotSession] 平台通用基础
*/
@MiraiInternalAPI
// cannot be internal because of `public BotSession`
abstract class BotSessionBase internal constructor(
val bot: Bot,
internal val sessionKey: SessionKey
) {
val socket: DataPacketSocketAdapter get() = bot.network.socket
val NetworkScope: CoroutineScope get() = bot.network
/**
* Web api 使用
*/
val cookies: String get() = _cookies
/**
* Web api 使用
*/
val sKey: String get() = _sKey
/**
* Web api 使用
*/
val gtk: Int get() = _gtk
inline fun Int.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0).toUInt())
inline fun Long.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0))
inline fun UInt.qq(): QQ = bot.getQQ(this)
suspend inline fun Int.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0).toUInt())
suspend inline fun Long.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0))
suspend inline fun UInt.group(): Group = bot.getGroup(GroupId(this))
suspend inline fun GroupId.group(): Group = bot.getGroup(this)
suspend inline fun GroupInternalId.group(): Group = bot.getGroup(this)
suspend fun Image.getLink(): ImageLink = when (this.id) {
is ImageId0x06 -> FriendImagePacket.RequestImageLink(bot.qqAccount, bot.sessionKey, id).sendAndExpect<FriendImageLink>()
is ImageId0x03 -> GroupImagePacket.RequestImageLink(bot.qqAccount, bot.sessionKey, id).sendAndExpect<GroupImageLink>().requireSuccess()
else -> assertUnreachable()
}
suspend inline fun Image.downloadAsByteArray(): ByteArray = getLink().downloadAsByteArray()
suspend inline fun Image.download(): ByteReadPacket = getLink().download()
// region internal
@Suppress("PropertyName")
internal var _sKey: String = ""
set(value) {
field = value
_gtk = getGTK(value)
}
@Suppress("PropertyName")
internal lateinit var _cookies: String
private var _gtk: Int = 0
/**
* 发送一个数据包, 并期待接受一个特定的 [ServerPacket][P].
* 这个方法会立即发出这个数据包然后返回一个 [CompletableDeferred].
*
* 实现方法:
* ```kotlin
* with(session){
* ClientPacketXXX(...).sendAndExpectAsync<ServerPacketXXX> {
* //it: ServerPacketXXX
* }
* }
* ```
* @sample net.mamoe.mirai.network.protocol.timpc.packet.action.uploadImage
*
* @param checkSequence 是否筛选 `sequenceId`, 即是否筛选发出的包对应的返回包.
* @param P 期待的包
* @param handler 处理期待的包. **将会在调用本函数的 [coroutineContext] 下执行.**
*
* @see Bot.withSession 转换 receiver, 即 `this` 的指向, 为 [BotSession]
*/
internal suspend inline fun <reified P : Packet, R> OutgoingPacket.sendAndExpectAsync(
checkSequence: Boolean = true,
noinline handler: suspend (P) -> R
): Deferred<R> {
val deferred: CompletableDeferred<R> = CompletableDeferred(coroutineContext[Job])
(bot.network as TIMBotNetworkHandler)
.addHandler(TemporaryPacketHandler(P::class, deferred, this@BotSessionBase as BotSession, checkSequence, coroutineContext + deferred).also {
it.toSend(this)
it.onExpect(handler)
})
return deferred
}
internal suspend inline fun <reified P : Packet> OutgoingPacket.sendAndExpectAsync(checkSequence: Boolean = true): Deferred<P> =
sendAndExpectAsync<P, P>(checkSequence) { it }
internal suspend inline fun <reified P : Packet, R> OutgoingPacket.sendAndExpect(
checkSequence: Boolean = true,
timeoutMillis: Long = 5.secondsToMillis,
crossinline mapper: (P) -> R
): R = withTimeout(timeoutMillis) { sendAndExpectAsync<P, R>(checkSequence) { mapper(it) }.await() }
internal suspend inline fun <reified P : Packet> OutgoingPacket.sendAndExpect(
checkSequence: Boolean = true,
timeoutMillist: Long = 5.secondsToMillis
): P = withTimeout(timeoutMillist) { sendAndExpectAsync<P, P>(checkSequence) { it }.await() }
internal suspend inline fun OutgoingPacket.send() =
(socket as TIMBotNetworkHandler.BotSocketAdapter).sendPacket(this)
// endregion
}
inline val BotSession.isOpen: Boolean get() = socket.isOpen
inline val BotSession.qqAccount: UInt get() = bot.account.id // 为了与群和好友的 id 区别.
/**
* 取得 [BotNetworkHandler] 的 sessionKey.
* 实际上是一个捷径.
*/
internal inline val BotNetworkHandler<*>.sessionKey: SessionKey get() = this.session.sessionKey
/**
* 取得 [Bot] 的 [BotSession].
* 实际上是一个捷径.
*/
inline val Bot.session: BotSession get() = this.network.session
/**
* 取得 [Bot] 的 `sessionKey`.
* 实际上是一个捷径.
*/
internal inline val Bot.sessionKey: SessionKey get() = this.session.sessionKey
/**
* 发送数据包
* @throws IllegalStateException 当 [BotNetworkHandler.socket] 未开启时
*/
internal suspend inline fun BotSession.sendPacket(packet: OutgoingPacket) = this.bot.sendPacket(packet)
inline fun BotSession.getQQ(@PositiveNumbers number: Long): QQ = this.bot.getQQ(number)
inline fun BotSession.getQQ(number: UInt): QQ = this.bot.getQQ(number)
suspend inline fun BotSession.getGroup(id: UInt): Group = this.bot.getGroup(id)
suspend inline fun BotSession.getGroup(@PositiveNumbers id: Long): Group = this.bot.getGroup(id)
suspend inline fun BotSession.getGroup(id: GroupId): Group = this.bot.getGroup(id)
suspend inline fun BotSession.getGroup(internalId: GroupInternalId): Group = this.bot.getGroup(internalId)
\ No newline at end of file
@file:JvmMultifileClass
@file:JvmName("BotHelperKt")
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.data
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
@Suppress("ClassName")
sealed class AddFriendResult {
abstract class DONE internal constructor() : AddFriendResult() {
override fun toString(): String = "AddFriendResult(Done)"
}
/**
* 对方拒绝添加好友
*/
object REJECTED : AddFriendResult() {
override fun toString(): String = "AddFriendResult(Rejected)"
}
/**
* 这个人已经是好友
*/
object ALREADY_ADDED : DONE() {
override fun toString(): String = "AddFriendResult(AlreadyAdded)"
}
/**
* 等待对方同意
*/
object WAITING_FOR_APPROVE : DONE() {
override fun toString(): String = "AddFriendResult(WaitingForApprove)"
}
/**
* 成功添加 (只在对方设置为允许任何人直接添加为好友时才会获得这个结果)
*/
object ADDED : DONE() {
override fun toString(): String = "AddFriendResult(Added)"
}
}
\ No newline at end of file
package net.mamoe.mirai.network.protocol.timpc.packet.event package net.mamoe.mirai.network.data
import net.mamoe.mirai.event.Subscribable import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.network.protocol.timpc.packet.Packet
/** /**
* 事件包. 可被监听. * 事件包. 可被监听.
......
package net.mamoe.mirai.network.data
/**
* 给好友设置的备注
*/
inline class FriendNameRemark(val value: String) : Packet
package net.mamoe.mirai.network.data
import net.mamoe.mirai.contact.ContactList
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import kotlin.jvm.JvmField
/**
* 群资料
*/
@Suppress("MemberVisibilityCanBePrivate") // 将来使用
class GroupInfo(
@JvmField internal var _group: Group,
@JvmField internal var _owner: Member,
@JvmField internal var _name: String,
@JvmField internal var _announcement: String,
@JvmField internal var _members: ContactList<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}"
}
package net.mamoe.mirai.network.data
import io.ktor.client.request.get
import io.ktor.util.KtorExperimentalAPI
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.utils.Http
interface ImageLink {
/**
* 原图
*/
val original: String
suspend fun downloadAsByteArray(): ByteArray = download().readBytes()
@UseExperimental(KtorExperimentalAPI::class)
suspend fun download(): ByteReadPacket = Http.get(original)
}
\ No newline at end of file
@file:Suppress("unused") @file:Suppress("unused")
package net.mamoe.mirai.network.protocol.timpc.packet.login package net.mamoe.mirai.network.data
import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult.SUCCESS
import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.BotConfiguration
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/** /**
* 登录结果. 除 [SUCCESS] 外均为失败. * 登录失败的原因
* @see LoginResult.requireSuccess 要求成功
*/ */
enum class LoginResult(val id: Byte) { enum class LoginResult(val id: Byte) {
/**
* 登录成功
*/
SUCCESS(0),
/** /**
* 密码错误 * 密码错误
*/ */
...@@ -72,53 +62,4 @@ enum class LoginResult(val id: Byte) { ...@@ -72,53 +62,4 @@ enum class LoginResult(val id: Byte) {
* 该号码长期未登录, 为了保证账号安全, 已被系统设置成保护状态, 请用手机 TIM 最新版本登录, 登录成功后即可自动解除保护模式 * 该号码长期未登录, 为了保证账号安全, 已被系统设置成保护状态, 请用手机 TIM 最新版本登录, 登录成功后即可自动解除保护模式
*/ // TIM的错误代码为 00020 */ // TIM的错误代码为 00020
PROTECTED(11), PROTECTED(11),
} }
\ No newline at end of file
/**
* 如果 [this] 不为 [LoginResult.SUCCESS] 就抛出消息为 [lazyMessage] 的 [IllegalStateException]
*/
@UseExperimental(ExperimentalContracts::class)
inline fun LoginResult.requireSuccess(lazyMessage: (LoginResult) -> String) {
contract {
callsInPlace(lazyMessage, InvocationKind.AT_MOST_ONCE)
}
if (this != SUCCESS) error(lazyMessage(this))
}
/**
* 检查 [this] 为 [LoginResult.SUCCESS].
* 失败则 [error]
*/
@Suppress("NOTHING_TO_INLINE")
inline fun LoginResult.requireSuccess() = requireSuccess { "Login failed: $this" }
/**
* 检查 [this] 为 [LoginResult.SUCCESS].
* 失败则返回 `null`
*
* @return 成功时 [Unit], 失败时 `null`
*/
@Suppress("NOTHING_TO_INLINE")
inline fun LoginResult.requireSuccessOrNull(): Unit? = if (this == SUCCESS) Unit else null
/**
* 返回 [this] 是否为 [LoginResult.SUCCESS].
*/
@Suppress("NOTHING_TO_INLINE")
@UseExperimental(ExperimentalContracts::class)
inline fun LoginResult.isSuccess(): Boolean = this == SUCCESS
/**
* 检查 [this] 为 [LoginResult.SUCCESS].
* 失败则返回 `null`
*
* @return 成功时 [Unit], 失败时 `null`
*/
@UseExperimental(ExperimentalContracts::class)
inline fun <R> LoginResult.ifFail(block: (LoginResult) -> R): R? {
contract {
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
}
return if (this != SUCCESS) block(this) else null
}
package net.mamoe.mirai.network.data
/**
* 一个包的数据 (body)
*/
interface Packet
package net.mamoe.mirai.network.data
/**
* 曾用名列表
*
* 曾用名可能是:
* - 昵称
* - 共同群内的群名片
*/
class PreviousNameList(
list: List<String>
) : Packet, List<String> by list {
override fun toString(): String = this.joinToString(prefix = "PreviousNameList(", postfix = ")", separator = ", ")
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE") @file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.contact.data package net.mamoe.mirai.network.data
import io.ktor.util.date.GMTDate import io.ktor.util.date.GMTDate
...@@ -9,7 +9,7 @@ import io.ktor.util.date.GMTDate ...@@ -9,7 +9,7 @@ import io.ktor.util.date.GMTDate
*/ */
@Suppress("PropertyName") @Suppress("PropertyName")
data class Profile( data class Profile(
val qq: UInt, val qq: Long,
val nickname: String, val nickname: String,
val englishName: String?, val englishName: String?,
val chineseName: String?, val chineseName: String?,
......
package net.mamoe.mirai.network.protocol.timpc.packet.event
//来自 Android 给我的电脑发消息, ID 0211, 内容 "你好"
//00 00 00 20 00 05 00 02 00 01 00 06 00 04 00 01 01 01 00 09 00 06 03 E9 20 02 EB 94 00 0A 00 04 01 00 00 00 00 00 00 54 00 00 00 3E 08 12 1A 16 08 07 10 91 04 18 DD F1 92 B7 07 20 83 76 38 DD F1 92 B7 07 50 04 42 21 08 DD F1 92 B7 07 10 DD F1 92 B7 07 18 01 20 07 40 DC 85 96 EE 05 48 DC 85 96 EE 05 50 FA AF FD 18 52 15 0A 06 08 01 10 00 50 01 1A 0B 08 E9 07 10 94 D7 8B 80 02 50 02 08 04 12 1D 08 E9 07 10 94 D7 8B 80 02 18 01 20 01 28 DD F1 92 B7 07 30 DD F1 92 B7 07 48 02 50 01 32 1B 08 80 80 B8 AE B5 02 10 01 18 00 20 01 2A 0C 0A 0A 08 01 12 06 E4 BD A0 E5 A5 BD
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.timpc.packet.event
...@@ -9,7 +9,12 @@ import kotlin.annotation.AnnotationTarget.* ...@@ -9,7 +9,12 @@ import kotlin.annotation.AnnotationTarget.*
* 非常不建议在发行版本中使用这些 API. * 非常不建议在发行版本中使用这些 API.
*/ */
@Experimental(level = Experimental.Level.ERROR) @Experimental(level = Experimental.Level.ERROR)
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) @Target(
CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR,
CLASS,
FUNCTION,
PROPERTY
)
annotation class MiraiInternalAPI annotation class MiraiInternalAPI
/** /**
......
...@@ -2,7 +2,6 @@ package net.mamoe.mirai.utils ...@@ -2,7 +2,6 @@ package net.mamoe.mirai.utils
import kotlinx.io.core.IoBuffer import kotlinx.io.core.IoBuffer
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.timpc.packet.login.TouchPacket.TouchResponse
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
import kotlin.jvm.JvmStatic import kotlin.jvm.JvmStatic
...@@ -24,9 +23,9 @@ expect var DefaultCaptchaSolver: CaptchaSolver ...@@ -24,9 +23,9 @@ expect var DefaultCaptchaSolver: CaptchaSolver
*/ */
class BotConfiguration : CoroutineContext.Element { class BotConfiguration : CoroutineContext.Element {
/** /**
* 等待 [TouchResponse] 的时间 * 连接每个服务器的时间
*/ */
var touchTimeoutMillis: Long = 2.secondsToMillis var touchTimeoutMillis: Long = 1.secondsToMillis
/** /**
* 是否使用随机的设备名. * 是否使用随机的设备名.
* 使用随机可以降低被封禁的风险, 但可能导致每次登录都需要输入验证码 * 使用随机可以降低被封禁的风险, 但可能导致每次登录都需要输入验证码
......
...@@ -7,7 +7,7 @@ import net.mamoe.mirai.utils.io.getRandomByteArray ...@@ -7,7 +7,7 @@ import net.mamoe.mirai.utils.io.getRandomByteArray
private const val GTK_BASE_VALUE: Int = 5381 private const val GTK_BASE_VALUE: Int = 5381
internal fun getGTK(sKey: String): Int { fun getGTK(sKey: String): Int {
var value = GTK_BASE_VALUE var value = GTK_BASE_VALUE
for (c in sKey.toByteArray()) { for (c in sKey.toByteArray()) {
value += (value shl 5) + c.toInt() value += (value shl 5) + c.toInt()
...@@ -18,14 +18,11 @@ internal fun getGTK(sKey: String): Int { ...@@ -18,14 +18,11 @@ internal fun getGTK(sKey: String): Int {
} }
@Tested @Tested
@PublishedApi fun BytePacketBuilder.writeCRC32() = writeCRC32(getRandomByteArray(16))
internal fun BytePacketBuilder.writeCRC32() = writeCRC32(getRandomByteArray(16))
@PublishedApi fun BytePacketBuilder.writeCRC32(key: ByteArray) {
internal fun BytePacketBuilder.writeCRC32(key: ByteArray) {
writeFully(key)//key writeFully(key)//key
writeInt(crc32(key)) writeInt(crc32(key))
} }
@PublishedApi fun md5(str: String): ByteArray = md5(str.toByteArray())
internal fun md5(str: String): ByteArray = md5(str.toByteArray()) \ No newline at end of file
\ No newline at end of file
...@@ -7,8 +7,7 @@ import kotlinx.io.core.Input ...@@ -7,8 +7,7 @@ import kotlinx.io.core.Input
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.network.protocol.timpc.packet.action.uploadImage
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.io.toUHexString
@Suppress("FunctionName") @Suppress("FunctionName")
...@@ -50,7 +49,14 @@ class ExternalImage( ...@@ -50,7 +49,14 @@ class ExternalImage(
* 用于发送消息的 [ImageId] * 用于发送消息的 [ImageId]
*/ */
@Suppress("EXPERIMENTAL_UNSIGNED_LITERALS") @Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
val groupImageId: ImageId by lazy { ImageId0x03("{${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}}.$format", 0u, height, width) } val groupImageId: ImageId by lazy {
ImageId0x03(
"{${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}}.$format",
0u,
height,
width
)
}
override fun toString(): String = "[ExternalImage(${width}x$height $format)]" override fun toString(): String = "[ExternalImage(${width}x$height $format)]"
} }
......
@file:Suppress("NOTHING_TO_INLINE") @file:Suppress("NOTHING_TO_INLINE", "unused")
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
......
package net.mamoe.mirai.utils
import net.mamoe.mirai.network.data.LoginResult
class LoginFailedException(
val result: LoginResult,
message: String = "Login failed with reason $result"
) : Exception(message)
\ No newline at end of file
...@@ -8,7 +8,7 @@ import kotlin.jvm.JvmStatic ...@@ -8,7 +8,7 @@ import kotlin.jvm.JvmStatic
* QQ 在线状态 * QQ 在线状态
* *
* @author Him188moe * @author Him188moe
* @see net.mamoe.mirai.network.protocol.timpc.packet.login.ChangeOnlineStatusPacket * @see net.mamoe.mirai.timpc.network.packet.login.ChangeOnlineStatusPacket
*/ */
inline class OnlineStatus( inline class OnlineStatus(
inline val id: UByte inline val id: UByte
......
package net.mamoe.mirai.utils
/**
* 图片文件过大
*/
class OverFileSizeMaxException : IllegalStateException()
...@@ -4,7 +4,6 @@ package net.mamoe.mirai.utils ...@@ -4,7 +4,6 @@ package net.mamoe.mirai.utils
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.util.date.GMTDate import io.ktor.util.date.GMTDate
import kotlinx.io.core.IoBuffer
/** /**
* 时间戳 * 时间戳
...@@ -44,5 +43,4 @@ expect fun localIpAddress(): String ...@@ -44,5 +43,4 @@ expect fun localIpAddress(): String
/** /**
* Ktor HttpClient. 不同平台使用不同引擎. * Ktor HttpClient. 不同平台使用不同引擎.
*/ */
@PublishedApi expect val Http: HttpClient
internal expect val Http: HttpClient
...@@ -10,9 +10,13 @@ import kotlin.coroutines.CoroutineContext ...@@ -10,9 +10,13 @@ import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
/** /**
* 创建一个在当前 [CoroutineScope] 下执行初始化的 [SuspendLazy] * 创建一个在当前 [CoroutineScope] 下执行初始化的 [suspendLazy]
*
* ```kotlin
* val image: Deferred<Image> by suspendLazy{ /* intializer */ }
* ```
*/ */
fun <R> CoroutineScope.SuspendLazy(context: CoroutineContext = EmptyCoroutineContext, initializer: suspend () -> R): Lazy<Deferred<R>> = fun <R> CoroutineScope.suspendLazy(context: CoroutineContext = EmptyCoroutineContext, initializer: suspend () -> R): Lazy<Deferred<R>> =
SuspendLazy(this, context, initializer) SuspendLazy(this, context, initializer)
/** /**
......
...@@ -3,8 +3,6 @@ package net.mamoe.mirai.utils ...@@ -3,8 +3,6 @@ package net.mamoe.mirai.utils
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.IoBuffer import kotlinx.io.core.IoBuffer
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.network.protocol.timpc.packet.Decrypter
import net.mamoe.mirai.network.protocol.timpc.packet.DecrypterByteArray
import net.mamoe.mirai.utils.io.ByteArrayPool import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.toByteArray import net.mamoe.mirai.utils.io.toByteArray
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.io.toUHexString
...@@ -17,7 +15,7 @@ import kotlin.random.Random ...@@ -17,7 +15,7 @@ import kotlin.random.Random
/** /**
* 解密错误 * 解密错误
*/ */
internal class DecryptionFailedException : Exception { class DecryptionFailedException : Exception {
constructor() : super() constructor() : super()
constructor(message: String?) : super(message) constructor(message: String?) : super(message)
} }
...@@ -31,9 +29,7 @@ internal class DecryptionFailedException : Exception { ...@@ -31,9 +29,7 @@ internal class DecryptionFailedException : Exception {
* @param key 长度至少为 16 * @param key 长度至少为 16
* @throws DecryptionFailedException 解密错误时 * @throws DecryptionFailedException 解密错误时
*/ */
internal fun ByteArray.encryptBy(key: ByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key, sourceLength = length) fun ByteArray.encryptBy(key: ByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key, sourceLength = length)
internal fun ByteArray.encryptBy(key: DecrypterByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key.value, sourceLength = length)
/** /**
* 在 [ByteArrayPool] 缓存 [this], 然后使用 [key] 加密. * 在 [ByteArrayPool] 缓存 [this], 然后使用 [key] 加密.
...@@ -42,7 +38,7 @@ internal fun ByteArray.encryptBy(key: DecrypterByteArray, length: Int = this.siz ...@@ -42,7 +38,7 @@ internal fun ByteArray.encryptBy(key: DecrypterByteArray, length: Int = this.siz
* @consumer 由于缓存需要被回收, 需在方法内执行解密后明文的消耗过程 * @consumer 由于缓存需要被回收, 需在方法内执行解密后明文的消耗过程
* @throws DecryptionFailedException 解密错误时 * @throws DecryptionFailedException 解密错误时
*/ */
internal inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, length: Int = remaining.toInt() - offset, consumer: (ByteArray) -> Unit) { inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, length: Int = remaining.toInt() - offset, consumer: (ByteArray) -> Unit) {
ByteArrayPool.useInstance { ByteArrayPool.useInstance {
this.readFully(it, offset, length) this.readFully(it, offset, length)
consumer(it.encryptBy(key, length = length)) consumer(it.encryptBy(key, length = length))
...@@ -60,7 +56,7 @@ internal inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, le ...@@ -60,7 +56,7 @@ internal inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, le
* @param key 固定长度 16 * @param key 固定长度 16
* @throws DecryptionFailedException 解密错误时 * @throws DecryptionFailedException 解密错误时
*/ */
internal fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteArray = fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteArray =
TEA.decrypt(checkDataLengthAndReturnSelf(length), key, sourceLength = length) TEA.decrypt(checkDataLengthAndReturnSelf(length), key, sourceLength = length)
/** /**
...@@ -71,7 +67,7 @@ internal fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteA ...@@ -71,7 +67,7 @@ internal fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteA
* @param key 长度至少为 16 * @param key 长度至少为 16
* @throws DecryptionFailedException 解密错误时 * @throws DecryptionFailedException 解密错误时
*/ */
internal fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteArray { fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteArray {
checkDataLengthAndReturnSelf(length) checkDataLengthAndReturnSelf(length)
return ByteArrayPool.useInstance { keyBuffer -> return ByteArrayPool.useInstance { keyBuffer ->
key.readFully(keyBuffer, 0, key.readRemaining) key.readFully(keyBuffer, 0, key.readRemaining)
...@@ -85,7 +81,7 @@ internal fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteAr ...@@ -85,7 +81,7 @@ internal fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteAr
* @param key 长度至少为 16 * @param key 长度至少为 16
* @throws DecryptionFailedException 解密错误时 * @throws DecryptionFailedException 解密错误时
*/ */
internal fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemaining - offset): ByteArray { fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemaining - offset): ByteArray {
return ByteArrayPool.useInstance { return ByteArrayPool.useInstance {
this.readFully(it, offset, length) this.readFully(it, offset, length)
it.checkDataLengthAndReturnSelf(length) it.checkDataLengthAndReturnSelf(length)
...@@ -97,20 +93,18 @@ internal fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = r ...@@ -97,20 +93,18 @@ internal fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = r
// region ByteReadPacket extension // region ByteReadPacket extension
internal fun ByteReadPacket.decryptBy(key: ByteArray): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) } fun ByteReadPacket.decryptBy(key: ByteArray): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
internal fun ByteReadPacket.decryptBy(key: IoBuffer): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
internal fun ByteReadPacket.decryptBy(key: Decrypter): ByteReadPacket = key.decrypt(this) fun ByteReadPacket.decryptBy(key: IoBuffer): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
internal inline fun <R> ByteReadPacket.decryptAsByteArray(key: ByteArray, consumer: (ByteArray) -> R): R = inline fun <R> ByteReadPacket.decryptAsByteArray(key: ByteArray, consumer: (ByteArray) -> R): R =
ByteArrayPool.useInstance { ByteArrayPool.useInstance {
val length = remaining.toInt() val length = remaining.toInt()
readFully(it, 0, length) readFully(it, 0, length)
consumer(it.decryptBy(key, length)) consumer(it.decryptBy(key, length))
}.also { close() } }.also { close() }
internal inline fun <R> ByteReadPacket.decryptAsByteArray(key: IoBuffer, consumer: (ByteArray) -> R): R = inline fun <R> ByteReadPacket.decryptAsByteArray(key: IoBuffer, consumer: (ByteArray) -> R): R =
ByteArrayPool.useInstance { ByteArrayPool.useInstance {
val length = remaining.toInt() val length = remaining.toInt()
readFully(it, 0, length) readFully(it, 0, length)
......
...@@ -39,12 +39,26 @@ expect class WeakRef<T>(referent: T) { ...@@ -39,12 +39,26 @@ expect class WeakRef<T>(referent: T) {
fun clear() fun clear()
} }
/**
* Indicates that the property is delegated by a [WeakRef]
*
* @see weakRef
*/
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.SOURCE)
annotation class WeakRefProperty
/** /**
* Provides a weak reference to [this] * Provides a weak reference to [this]
* The `getValue` for delegation returns [this] when [this] is not released by GC * The `getValue` for delegation returns [this] when [this] is not released by GC
*/ */
fun <T> T.weakRef(): WeakRef<T> = WeakRef(this) fun <T> T.weakRef(): WeakRef<T> = WeakRef(this)
/**
* Constructs an unsafe inline delegate for [this]
*/
fun <T> WeakRef<T>.unsafe(): UnsafeWeakRef<T> = UnsafeWeakRef(this)
/** /**
* Provides a weak reference to [this]. * Provides a weak reference to [this].
* The `getValue` for delegation throws an [IllegalStateException] if the referent is released by GC. Therefore it returns notnull value only * The `getValue` for delegation throws an [IllegalStateException] if the referent is released by GC. Therefore it returns notnull value only
......
@file:Suppress("ObjectPropertyName", "MayBeConstant", "NonAsciiCharacters", "SpellCheckingInspection", "unused", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.utils.internal
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.readUVarInt
import net.mamoe.mirai.utils.io.toUHexString
import kotlin.math.max
import kotlin.reflect.KProperty0
/**
* 匹配已知 hex 常量并格式化后打印到控制台.
*
* 低效率, 仅调试使用.
*/
internal fun String.printColorize(ignoreUntilFirstConst: Boolean): String = with(HexComparator) { colorize(ignoreUntilFirstConst) }
/**
* 比较两个 hex 并格式化后打印到控制台.
*
* 低效率, 仅调试使用.
*/
@MiraiInternalAPI
fun printCompareHex(hex1s: String, hex2s: String): String = with(HexComparator) { compare(hex1s.toUpperCase(), hex2s.toUpperCase()) }
data class NamedHexElement(
val name: String,
val value: String
)
/**
* 初始化用于匹配的 Hex 列表
*/
private fun LinkedHashSet<NamedHexElement>.initConstFileds() {
listOf(
TestConsts,
TIMProtocol,
PacketIds
).forEach { obj ->
obj::class.members.filterIsInstance<KProperty0<*>>().forEach { property ->
property.get()?.let { add(NamedHexElement(property.name, it.toString())) }
}
}
}
private object TestConsts {
val NIU_BI = "牛逼".toByteArray().toUHexString()
val _1994701021 = 1994701021u.toUHexString(" ")
val _1040400290 = 1040400290u.toUHexString(" ")
val _580266363 = 580266363u.toUHexString(" ")
val _761025446 = 761025446u.toUHexString()
val _1040400290_ = "3E 03 3F A2"
val _1994701021_ = "76 E4 B8 DD"
val _jiahua_ = "B1 89 BE 09"
val _Him188moe_ = "Him188moe".toByteArray().toUHexString()
val 发图片2 = "发图片2".toByteArray().toUHexString()
val 发图片群 = "发图片群".toByteArray().toUHexString()
val 发图片 = "发图片".toByteArray().toUHexString()
val 群 = "群".toByteArray().toUHexString()
val 你好 = "你好".toByteArray().toUHexString()
val MESSAGE_TAIL_10404 =
"0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00"
.replace(" ", " ")
val FONT_10404 = "E5 BE AE E8 BD AF E9 9B 85 E9 BB 91"
val varint580266363 = "FB D2 D8 94 02"
val varint1040400290 = "A2 FF 8C F0 03"
val varint1994701021 = "DD F1 92 B7 07"
val varint761025446 = 761025446u.toUHexString().hexToBytes().read { readUVarInt() }.toUHexString()
}
@Suppress("SpellCheckingInspection")
private object PacketIds {
val heartbeat = "00 58"
val friendmsgsend = "00 CD"
val friendmsgevent = "00 CE"
val groupmsg = "00 02"
}
/**
* Hex 比较器, 并着色已知常量
*
* This could be used to check packet encoding..
* but better to run under UNIX
*
* @author NaturalHG
* @author Him188moe
*/
private object HexComparator {
private val RED = "\u001b[31m"
private val GREEN = "\u001b[33m"
private val UNKNOWN_COLOR = "\u001b[30m"
private val BLUE = "\u001b[34m"
@Suppress("unused")
private class ConstMatcher constructor(hex: String) {
private val matches = linkedSetOf<Match>()
fun getMatchedConstName(hexNumber: Int): String? {
for (match in this.matches) {
if (match.range.contains(hexNumber)) {
return match.constName
}
}
return null
}
private class Match internal constructor(val range: IntRange, val constName: String)
init {
CONST_FIELDS.forEach { (name, value) ->
for (match in match(hex, value)) {
matches.add(Match(match, name))
}
}
TIMProtocol::class.members.filterIsInstance<KProperty0<*>>().mapNotNull { it()?.toString() }.forEach {
for (match in match(hex, it)) {
matches.add(Match(match, it))
}
}
}
companion object {
val CONST_FIELDS: Set<NamedHexElement> = linkedSetOf<NamedHexElement>().apply { initConstFileds() }
}
private fun match(hex: String, value: String): Set<IntRange> {
val constValue: String
try {
constValue = value.trim { it <= ' ' }
if (constValue.length / 3 <= 3) {//Minimum numbers of const hex bytes
return linkedSetOf()
}
} catch (ignored: ClassCastException) {
return linkedSetOf()
}
return mutableSetOf<IntRange>().apply {
var index = -1
index = hex.indexOf(constValue, index + 1)
while (index != -1) {
add(IntRange(index / 3, (index + constValue.length) / 3))
index = hex.indexOf(constValue, index + 1)
}
}
}
}
private fun buildConstNameChain(length: Int, constMatcher: ConstMatcher, constNameBuilder: StringBuilder) {
//System.out.println(constMatcher.matches);
var i = 0
while (i < length) {
constNameBuilder.append(" ")
val match = constMatcher.getMatchedConstName(i / 4)
if (match != null) {
var appendedNameLength = match.length
constNameBuilder.append(match)
while (match == constMatcher.getMatchedConstName(i++ / 4)) {
if (appendedNameLength-- < 0) {
constNameBuilder.append(" ")
}
}
constNameBuilder.append(" ".repeat(match.length % 4))
}
i++
}
}
fun compare(hex1s: String, hex2s: String): String {
val builder = StringBuilder()
val hex1 = hex1s.trim { it <= ' ' }.replace("\n", "").split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val hex2 = hex2s.trim { it <= ' ' }.replace("\n", "").split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val constMatcher1 = ConstMatcher(hex1s)
val constMatcher2 = ConstMatcher(hex2s)
if (hex1.size == hex2.size) {
builder.append(GREEN).append("长度一致:").append(hex1.size)
} else {
builder.append(RED).append("长度不一致").append(hex1.size).append("/").append(hex2.size)
}
val numberLine = StringBuilder()
val hex1ConstName = StringBuilder()
val hex1b = StringBuilder()
val hex2b = StringBuilder()
val hex2ConstName = StringBuilder()
var dif = 0
val length = max(hex1.size, hex2.size) * 4
buildConstNameChain(length, constMatcher1, hex1ConstName)
buildConstNameChain(length, constMatcher2, hex2ConstName)
for (i in 0 until max(hex1.size, hex2.size)) {
var h1: String? = null
var h2: String? = null
var isDif = false
if (hex1.size <= i) {
h1 = RED + "__"
isDif = true
} else {
val matchedConstName = constMatcher1.getMatchedConstName(i)
if (matchedConstName != null) {
h1 = BLUE + hex1[i]
}
}
if (hex2.size <= i) {
h2 = RED + "__"
isDif = true
} else {
val matchedConstName = constMatcher2.getMatchedConstName(i)
if (matchedConstName != null) {
h2 = BLUE + hex2[i]
}
}
if (h1 == null && h2 == null) {
h1 = hex1[i]
h2 = hex2[i]
if (h1 == h2) {
h1 = GREEN + h1
h2 = GREEN + h2
} else {
h1 = RED + h1
h2 = RED + h2
isDif = true
}
} else {
if (h1 == null) {
h1 = RED + hex1[i]
}
if (h2 == null) {
h2 = RED + hex2[i]
}
}
numberLine.append(UNKNOWN_COLOR).append(getFixedNumber(i)).append(" ")
hex1b.append(" ").append(h1).append(" ")
hex2b.append(" ").append(h2).append(" ")
if (isDif) {
++dif
}
//doConstReplacement(hex1b);
//doConstReplacement(hex2b);
}
return builder.append(" ").append(dif).append(" 个不同").append("\n")
.append(numberLine).append("\n")
.append(hex1ConstName).append("\n")
.append(hex1b).append("\n")
.append(hex2b).append("\n")
.append(hex2ConstName).append("\n")
.toString()
}
fun String.colorize(ignoreUntilFirstConst: Boolean = false): String {
val builder = StringBuilder()
val hex = trim { it <= ' ' }.replace("\n", "").split(" ").dropLastWhile { it.isEmpty() }.toTypedArray()
val constMatcher1 = ConstMatcher(this)
val numberLine = StringBuilder()
val hex1ConstName = StringBuilder()
val hex1b = StringBuilder()
buildConstNameChain(length, constMatcher1, hex1ConstName)
var firstConst: String? = null
var constNameOffset = 0//已经因为还没到第一个const跳过了几个char
for (i in hex.indices) {
var h1: String? = null
val matchedConstName = constMatcher1.getMatchedConstName(i)
if (matchedConstName != null) {
firstConst = firstConst ?: matchedConstName
h1 = BLUE + hex[i]
}
if (!ignoreUntilFirstConst || firstConst != null) {//有过任意一个 Const
if (h1 == null) {
h1 = GREEN + hex[i]
}
numberLine.append(UNKNOWN_COLOR).append(getFixedNumber(i)).append(" ")
hex1b.append(" ").append(h1).append(" ")
} else {
constNameOffset++
}
}
return builder.append("\n")
.append(numberLine).append("\n")
.append(if (firstConst == null) hex1ConstName else {
with(hex1ConstName) {
val index = indexOf(firstConst)
if (index == -1) toString() else " " + substring(index, length)
}
}).append("\n")
.append(hex1b).append("\n")
.toString()
}
private fun getFixedNumber(number: Int): String {
if (number < 10) {
return "00$number"
}
return if (number < 100) {
"0$number"
} else number.toString()
}
}
\ No newline at end of file
...@@ -4,8 +4,7 @@ package net.mamoe.mirai.utils.internal ...@@ -4,8 +4,7 @@ package net.mamoe.mirai.utils.internal
* 要求 [this] 最小为 [min]. * 要求 [this] 最小为 [min].
*/ */
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
@PublishedApi inline fun Int.coerceAtLeastOrFail(min: Int): Int {
internal inline fun Int.coerceAtLeastOrFail(min: Int): Int {
require(this >= min) require(this >= min)
return this return this
} }
...@@ -14,8 +13,7 @@ internal inline fun Int.coerceAtLeastOrFail(min: Int): Int { ...@@ -14,8 +13,7 @@ internal inline fun Int.coerceAtLeastOrFail(min: Int): Int {
* 要求 [this] 最小为 [min]. * 要求 [this] 最小为 [min].
*/ */
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
@PublishedApi inline fun Long.coerceAtLeastOrFail(min: Long): Long {
internal inline fun Long.coerceAtLeastOrFail(min: Long): Long {
require(this >= min) require(this >= min)
return this return this
} }
...@@ -24,20 +22,11 @@ internal inline fun Long.coerceAtLeastOrFail(min: Long): Long { ...@@ -24,20 +22,11 @@ internal inline fun Long.coerceAtLeastOrFail(min: Long): Long {
* 要求 [this] 最大为 [max]. * 要求 [this] 最大为 [max].
*/ */
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
@PublishedApi inline fun Int.coerceAtMostOrFail(max: Int): Int =
internal inline fun Int.coerceAtMostOrFail(max: Int): Int =
if (this >= max) error("value is greater than its expected maximum value $max") if (this >= max) error("value is greater than its expected maximum value $max")
else this else this
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
@PublishedApi inline fun Long.coerceAtMostOrFail(max: Long): Long =
internal inline fun Long.coerceAtMostOrFail(max: Long): Long =
if (this >= max) error("value is greater than its expected maximum value $max") if (this >= max) error("value is greater than its expected maximum value $max")
else this else this
\ No newline at end of file
/**
* 表示这个参数必须为正数. 仅用于警示
*/
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE)
internal annotation class PositiveNumbers
\ No newline at end of file
...@@ -6,7 +6,7 @@ import kotlinx.io.pool.ObjectPool ...@@ -6,7 +6,7 @@ import kotlinx.io.pool.ObjectPool
internal const val DEFAULT_BYTE_ARRAY_POOL_SIZE = 256 internal const val DEFAULT_BYTE_ARRAY_POOL_SIZE = 256
internal const val DEFAULT_BYTE_ARRAY_SIZE = 4096 internal const val DEFAULT_BYTE_ARRAY_SIZE = 4096
internal val ByteArrayPool: ObjectPool<ByteArray> = ByteArrayPoolImpl val ByteArrayPool: ObjectPool<ByteArray> = ByteArrayPoolImpl
private object ByteArrayPoolImpl : DefaultPool<ByteArray>(DEFAULT_BYTE_ARRAY_POOL_SIZE) { private object ByteArrayPoolImpl : DefaultPool<ByteArray>(DEFAULT_BYTE_ARRAY_POOL_SIZE) {
override fun produceInstance(): ByteArray = ByteArray(DEFAULT_BYTE_ARRAY_SIZE) override fun produceInstance(): ByteArray = ByteArray(DEFAULT_BYTE_ARRAY_SIZE)
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.utils.io package net.mamoe.mirai.utils.io
...@@ -7,33 +7,41 @@ import kotlinx.io.charsets.Charsets ...@@ -7,33 +7,41 @@ import kotlinx.io.charsets.Charsets
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.String import kotlinx.io.core.String
import kotlinx.io.core.use import kotlinx.io.core.use
import kotlin.jvm.JvmOverloads
import kotlin.jvm.JvmSynthetic @Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray
@UseExperimental(ExperimentalUnsignedTypes::class)
@JvmOverloads fun ByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String {
fun ByteArray.toHexString(separator: String = " "): String = this.joinToString(separator) { val lastIndex = offset + length
var ret = it.toString(16).toUpperCase() return buildString(length * 2) {
if (ret.length == 1) { this@toUHexString.forEachIndexed { index, it ->
ret = "0$ret" if (index in offset until lastIndex) {
var ret = it.toByte().toUByte().toString(16).toUpperCase()
if (ret.length == 1) ret = "0$ret"
append(ret)
if (index != lastIndex) append(separator)
}
}
} }
return@joinToString ret
} }
@JvmOverloads @Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray
fun ByteArray.toUHexString(separator: String = " "): String = this.toUByteArray().toUHexString(separator) @ExperimentalUnsignedTypes
fun UByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String {
fun ByteArray.encodeToString(charset: Charset = Charsets.UTF_8): String = String(this, charset = charset) val lastIndex = offset + length
return buildString(length * 2) {
@JvmSynthetic this@toUHexString.forEachIndexed { index, it ->
@JvmOverloads if (index in offset until lastIndex) {
fun UByteArray.toUHexString(separator: String = " "): String = this.joinToString(separator) { var ret = it.toByte().toUByte().toString(16).toUpperCase()
var ret = it.toString(16).toUpperCase() if (ret.length == 1) ret = "0$ret"
if (ret.length == 1) { append(ret)
ret = "0$ret" if (index != lastIndex) append(separator)
}
}
} }
return@joinToString ret
} }
fun ByteArray.encodeToString(charset: Charset = Charsets.UTF_8): String = String(this, charset = charset)
fun ByteArray.toReadPacket(offset: Int = 0, length: Int = this.size) = ByteReadPacket(this, offset = offset, length = length) fun ByteArray.toReadPacket(offset: Int = 0, length: Int = this.size) = ByteReadPacket(this, offset = offset, length = length)
inline fun <R> ByteArray.read(t: ByteReadPacket.() -> R): R = this.toReadPacket().use(t) inline fun <R> ByteArray.read(t: ByteReadPacket.() -> R): R = this.toReadPacket().use(t)
......
package net.mamoe.mirai.utils.io package net.mamoe.mirai.utils.io
import kotlinx.io.core.* import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.internal.printColorize
import net.mamoe.mirai.utils.internal.printCompareHex
import net.mamoe.mirai.utils.withSwitch import net.mamoe.mirai.utils.withSwitch
internal object DebugLogger : MiraiLogger by DefaultLogger("Packet Debug").withSwitch() object DebugLogger : MiraiLogger by DefaultLogger("Packet Debug").withSwitch()
internal fun Throwable.logStacktrace(message: String? = null) = DebugLogger.error(message, this) fun Throwable.logStacktrace(message: String? = null) = DebugLogger.error(message, this)
@PublishedApi fun debugPrintln(any: Any?) = DebugLogger.debug(any)
internal fun debugPrintln(any: Any?) = DebugLogger.debug(any)
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this")) fun String.debugPrint(name: String): String {
internal fun String.debugPrint(name: String): String {
DebugLogger.debug("$name=$this") DebugLogger.debug("$name=$this")
return this return this
} }
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this")) fun ByteArray.debugPrint(name: String): ByteArray {
internal fun ByteArray.debugPrint(name: String): ByteArray {
DebugLogger.debug(name + "=" + this.toUHexString()) DebugLogger.debug(name + "=" + this.toUHexString())
return this return this
} }
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this")) fun IoBuffer.debugPrint(name: String): IoBuffer {
internal fun IoBuffer.debugPrint(name: String): IoBuffer { ByteArrayPool.useInstance {
val readBytes = this.readBytes() val count = this.readAvailable(it)
DebugLogger.debug(name + "=" + readBytes.toUHexString()) DebugLogger.debug(name + "=" + it.toUHexString(offset = 0, length = count))
return readBytes.toIoBuffer() return it.toIoBuffer(0, count)
}
} }
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this")) inline fun IoBuffer.debugCopyUse(block: IoBuffer.() -> Unit): IoBuffer {
internal inline fun IoBuffer.debugCopyUse(block: IoBuffer.() -> Unit): IoBuffer { ByteArrayPool.useInstance {
val readBytes = this.readBytes() val count = this.readAvailable(it)
block(readBytes.toIoBuffer()) block(it.toIoBuffer(0, count))
return readBytes.toIoBuffer() return it.toIoBuffer(0, count)
}
} }
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("discardExact(n)")) fun Input.debugDiscardExact(n: Number, name: String = "") {
internal fun Input.debugDiscardExact(n: Number, name: String = "") {
DebugLogger.debug("Discarded($n) $name=" + this.readBytes(n.toInt()).toUHexString()) DebugLogger.debug("Discarded($n) $name=" + this.readBytes(n.toInt()).toUHexString())
} }
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this")) fun ByteReadPacket.debugPrint(name: String = ""): ByteReadPacket {
internal fun ByteReadPacket.debugPrint(name: String = ""): ByteReadPacket { ByteArrayPool.useInstance {
val bytes = this.readBytes() val count = this.readAvailable(it)
DebugLogger.debug("ByteReadPacket $name=" + bytes.toUHexString()) DebugLogger.debug("ByteReadPacket $name=" + it.toUHexString(offset = 0, length = count))
return bytes.toReadPacket() return it.toReadPacket(0, count)
}
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this"))
internal inline fun <R> ByteReadPacket.debugPrintIfFail(name: String = "", block: ByteReadPacket.() -> R): R {
val bytes = this.readBytes()
try {
return block(bytes.toReadPacket())
} catch (e: Throwable) {
DebugLogger.debug("Error in ByteReadPacket $name=" + bytes.toUHexString())
throw e
} }
} }
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this")) inline fun <R> ByteReadPacket.debugPrintIfFail(name: String = "", block: ByteReadPacket.() -> R): R {
internal fun ByteReadPacket.debugColorizedPrint(name: String = "", ignoreUntilFirstConst: Boolean = false): ByteReadPacket { ByteArrayPool.useInstance {
val bytes = this.readBytes() val count = this.readAvailable(it)
bytes.printColorizedHex(name, ignoreUntilFirstConst) try {
return bytes.toReadPacket() return block(it.toReadPacket(0, count))
} } catch (e: Throwable) {
DebugLogger.debug("Error in ByteReadPacket $name=" + it.toUHexString(offset = 0, length = count))
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith(" ")) throw e
internal fun BytePacketBuilder.debugColorizedPrintThis(name: String = "") { }
val data = this.build().readBytes()
data.printColorizedHex(name)
this.writeFully(data)
}
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith(" "))
internal fun BytePacketBuilder.debugColorizedPrintThis(name: String = "", compareTo: String? = null) {
val data = this.build().readBytes()
data.printColorizedHex(name, compareTo = compareTo)
this.writeFully(data)
}
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith(" "))
internal fun BytePacketBuilder.debugPrintThis(name: String = "") {
val data = this.build().readBytes()
data.printColorizedHex(name)
this.writeFully(data)
}
internal fun String.printStringFromHex() {
println(this.hexToBytes().encodeToString())
}
@UseExperimental(MiraiInternalAPI::class)
internal fun ByteArray.printColorizedHex(name: String = "", ignoreUntilFirstConst: Boolean = false, compareTo: String? = null) {
println("Hex比较 `$name`")
if (compareTo != null) {
println(printCompareHex(toUHexString(), compareTo))
} else {
println(toUHexString().printColorize(ignoreUntilFirstConst))
} }
println()
} }
\ No newline at end of file
...@@ -2,10 +2,13 @@ ...@@ -2,10 +2,13 @@
package net.mamoe.mirai.utils.io package net.mamoe.mirai.utils.io
import kotlinx.io.InputStream
import kotlinx.io.OutputStream import kotlinx.io.OutputStream
import kotlinx.io.core.* import kotlinx.io.core.*
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.contact.GroupId
import net.mamoe.mirai.contact.GroupInternalId
import net.mamoe.mirai.contact.groupId
import net.mamoe.mirai.contact.groupInternalId
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
...@@ -23,6 +26,14 @@ inline fun Input.discardExact(n: UByte) = this.discardExact(n.toInt()) ...@@ -23,6 +26,14 @@ inline fun Input.discardExact(n: UByte) = this.discardExact(n.toInt())
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun Input.discardExact(n: Byte) = this.discardExact(n.toInt()) inline fun Input.discardExact(n: Byte) = this.discardExact(n.toInt())
fun ByteReadPacket.transferTo(outputStream: OutputStream) {
ByteArrayPool.useInstance {
while (this.isNotEmpty) {
outputStream.write(it, 0, this.readAvailable(it))
}
}
}
fun ByteReadPacket.readRemainingBytes( fun ByteReadPacket.readRemainingBytes(
n: Int = remaining.toInt()//not that safe but adequate n: Int = remaining.toInt()//not that safe but adequate
): ByteArray = ByteArray(n).also { readAvailable(it, 0, n) } ): ByteArray = ByteArray(n).also { readAvailable(it, 0, n) }
...@@ -43,6 +54,11 @@ fun Input.readIP(): String = buildString(4 + 3) { ...@@ -43,6 +54,11 @@ fun Input.readIP(): String = buildString(4 + 3) {
fun Input.readPacket(length: Int): ByteReadPacket = this.readBytes(length).toReadPacket() fun Input.readPacket(length: Int): ByteReadPacket = this.readBytes(length).toReadPacket()
fun Input.readQQ(): Long = this.readUInt().toLong()
fun Input.readGroup(): Long = this.readUInt().toLong()
fun Input.readGroupId(): GroupId = this.readUInt().toLong().groupId()
fun Input.readGroupInternalId(): GroupInternalId = this.readUInt().toLong().groupInternalId()
fun Input.readUVarIntLVString(): String = String(this.readUVarIntByteArray()) fun Input.readUVarIntLVString(): String = String(this.readUVarIntByteArray())
fun Input.readUByteLVString(): String = String(this.readUByteLVByteArray()) fun Input.readUByteLVString(): String = String(this.readUByteLVByteArray())
...@@ -55,7 +71,7 @@ fun Input.readUByteLVByteArray(): ByteArray = this.readBytes(this.readUByte().to ...@@ -55,7 +71,7 @@ fun Input.readUByteLVByteArray(): ByteArray = this.readBytes(this.readUByte().to
fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt()) fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt())
internal inline fun <R> inline(block: () -> R): R = block() private inline fun <R> inline(block: () -> R): R = block()
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode")
fun Input.readTLVMap(expectingEOF: Boolean = false, tagSize: Int = 1): MutableMap<UInt, ByteArray> { fun Input.readTLVMap(expectingEOF: Boolean = false, tagSize: Int = 1): MutableMap<UInt, ByteArray> {
...@@ -144,9 +160,6 @@ fun Map<UInt, ByteArray>.printTLVMap(name: String = "", keyLength: Int = 1) = ...@@ -144,9 +160,6 @@ fun Map<UInt, ByteArray>.printTLVMap(name: String = "", keyLength: Int = 1) =
internal inline fun unsupported(message: String? = null): Nothing = error(message ?: "Unsupported") internal inline fun unsupported(message: String? = null): Nothing = error(message ?: "Unsupported")
internal inline fun ByteReadPacket.unsupportedFlag(name: String, flag: String): Nothing = error("Unsupported flag of $name. flag=$flag, remaining=${readBytes().toUHexString()}")
internal inline fun ByteReadPacket.unsupportedType(name: String, type: String): Nothing = error("Unsupported type of $name. type=$type, remaining=${readBytes().toUHexString()}")
internal inline fun illegalArgument(message: String? = null): Nothing = error(message ?: "Illegal argument passed") internal inline fun illegalArgument(message: String? = null): Nothing = error(message ?: "Illegal argument passed")
@JvmName("printTLVStringMap") @JvmName("printTLVStringMap")
......
...@@ -8,54 +8,52 @@ import kotlinx.serialization.SerializationStrategy ...@@ -8,54 +8,52 @@ import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.contact.GroupId import net.mamoe.mirai.contact.GroupId
import net.mamoe.mirai.contact.GroupInternalId import net.mamoe.mirai.contact.GroupInternalId
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol import net.mamoe.mirai.utils.Tested
import net.mamoe.mirai.network.protocol.timpc.packet.DecrypterByteArray import net.mamoe.mirai.utils.currentTime
import net.mamoe.mirai.network.protocol.timpc.packet.login.PrivateKey import net.mamoe.mirai.utils.deviceName
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.encryptBy
import net.mamoe.mirai.utils.internal.coerceAtMostOrFail import net.mamoe.mirai.utils.internal.coerceAtMostOrFail
import kotlin.random.Random import kotlin.random.Random
import kotlin.random.nextInt import kotlin.random.nextInt
internal fun BytePacketBuilder.writeZero(count: Int) { fun BytePacketBuilder.writeZero(count: Int) {
require(count != 0) { "Trying to write zero with count 0, you made a mistake?" } require(count != 0) { "Trying to write zero with count 0, you made a mistake?" }
require(count > 0) { "writeZero: count must > 0" } require(count > 0) { "writeZero: count must > 0" }
repeat(count) { this.writeByte(0) } repeat(count) { this.writeByte(0) }
} }
internal fun BytePacketBuilder.writeRandom(length: Int) = repeat(length) { this.writeByte(Random.Default.nextInt(255).toByte()) } fun BytePacketBuilder.writeRandom(length: Int) = repeat(length) { this.writeByte(Random.Default.nextInt(255).toByte()) }
internal fun BytePacketBuilder.writeQQ(qq: Long) = this.writeUInt(qq.toUInt()) fun BytePacketBuilder.writeQQ(qq: Long) = this.writeUInt(qq.toUInt()) // same bit rep.
internal fun BytePacketBuilder.writeQQ(qq: UInt) = this.writeUInt(qq) fun BytePacketBuilder.writeGroup(groupId: GroupId) = this.writeUInt(groupId.value.toUInt())
internal fun BytePacketBuilder.writeGroup(groupId: GroupId) = this.writeUInt(groupId.value) fun BytePacketBuilder.writeGroup(groupInternalId: GroupInternalId) = this.writeUInt(groupInternalId.value.toUInt())
internal fun BytePacketBuilder.writeGroup(groupInternalId: GroupInternalId) = this.writeUInt(groupInternalId.value)
internal fun BytePacketBuilder.writeFully(value: DecrypterByteArray) = this.writeFully(value.value)
internal fun BytePacketBuilder.writeShortLVByteArray(byteArray: ByteArray) { fun BytePacketBuilder.writeShortLVByteArray(byteArray: ByteArray) {
this.writeShort(byteArray.size.toShort()) this.writeShort(byteArray.size.toShort())
this.writeFully(byteArray) this.writeFully(byteArray)
} }
internal fun BytePacketBuilder.writeShortLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) = fun BytePacketBuilder.writeShortLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) =
BytePacketBuilder().apply(builder).build().use { BytePacketBuilder().apply(builder).build().use {
if (tag != null) writeUByte(tag) if (tag != null) writeUByte(tag)
writeUShort((lengthOffset?.invoke(it.remaining) ?: it.remaining).coerceAtMostOrFail(0xFFFFL).toUShort()) writeUShort((lengthOffset?.invoke(it.remaining) ?: it.remaining).coerceAtMostOrFail(0xFFFFL).toUShort())
writePacket(it) writePacket(it)
} }
internal fun BytePacketBuilder.writeUVarIntLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) = fun BytePacketBuilder.writeUVarIntLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) =
BytePacketBuilder().apply(builder).build().use { BytePacketBuilder().apply(builder).build().use {
if (tag != null) writeUByte(tag) if (tag != null) writeUByte(tag)
writeUVarInt((lengthOffset?.invoke(it.remaining) ?: it.remaining).coerceAtMostOrFail(0xFFFFL)) writeUVarInt((lengthOffset?.invoke(it.remaining) ?: it.remaining).coerceAtMostOrFail(0xFFFFL))
writePacket(it) writePacket(it)
} }
internal fun BytePacketBuilder.writeShortLVString(str: String) = writeShortLVByteArray(str.toByteArray()) fun BytePacketBuilder.writeShortLVString(str: String) = writeShortLVByteArray(str.toByteArray())
internal fun BytePacketBuilder.writeIP(ip: String) = writeFully(ip.trim().split(".").map { it.toUByte() }.toUByteArray()) fun BytePacketBuilder.writeIP(ip: String) = writeFully(ip.trim().split(".").map { it.toUByte() }.toUByteArray())
internal fun BytePacketBuilder.writeTime() = this.writeInt(currentTime.toInt()) fun BytePacketBuilder.writeTime() = this.writeInt(currentTime.toInt())
internal fun BytePacketBuilder.writeHex(uHex: String) { fun BytePacketBuilder.writeHex(uHex: String) {
uHex.split(" ").forEach { uHex.split(" ").forEach {
if (it.isNotBlank()) { if (it.isNotBlank()) {
writeUByte(it.toUByte(16)) writeUByte(it.toUByte(16))
...@@ -63,49 +61,49 @@ internal fun BytePacketBuilder.writeHex(uHex: String) { ...@@ -63,49 +61,49 @@ internal fun BytePacketBuilder.writeHex(uHex: String) {
} }
} }
internal fun <T> BytePacketBuilder.writeProto(serializer: SerializationStrategy<T>, obj: T) = writeFully(ProtoBuf.dump(serializer, obj)) fun <T> BytePacketBuilder.writeProto(serializer: SerializationStrategy<T>, obj: T) = writeFully(ProtoBuf.dump(serializer, obj))
internal fun BytePacketBuilder.writeTLV(tag: UByte, values: UByteArray) { fun BytePacketBuilder.writeTLV(tag: UByte, values: UByteArray) {
writeUByte(tag) writeUByte(tag)
writeUVarInt(values.size.toUInt()) writeUVarInt(values.size.toUInt())
writeFully(values) writeFully(values)
} }
internal fun BytePacketBuilder.writeTLV(tag: UByte, values: ByteArray) { fun BytePacketBuilder.writeTLV(tag: UByte, values: ByteArray) {
writeUByte(tag) writeUByte(tag)
writeUVarInt(values.size.toUInt()) writeUVarInt(values.size.toUInt())
writeFully(values) writeFully(values)
} }
internal fun BytePacketBuilder.writeTHex(tag: UByte, uHex: String) { fun BytePacketBuilder.writeTHex(tag: UByte, uHex: String) {
this.writeUByte(tag) this.writeUByte(tag)
this.writeFully(uHex.hexToUBytes()) this.writeFully(uHex.hexToUBytes())
} }
internal fun BytePacketBuilder.writeTV(tagValue: UShort) = writeUShort(tagValue) fun BytePacketBuilder.writeTV(tagValue: UShort) = writeUShort(tagValue)
internal fun BytePacketBuilder.writeTV(tag: UByte, value: UByte) { fun BytePacketBuilder.writeTV(tag: UByte, value: UByte) {
writeUByte(tag) writeUByte(tag)
writeUByte(value) writeUByte(value)
} }
internal fun BytePacketBuilder.writeTUbyte(tag: UByte, value: UByte) { fun BytePacketBuilder.writeTUbyte(tag: UByte, value: UByte) {
this.writeUByte(tag) this.writeUByte(tag)
this.writeUByte(value) this.writeUByte(value)
} }
internal fun BytePacketBuilder.writeTUVarint(tag: UByte, value: UInt) { fun BytePacketBuilder.writeTUVarint(tag: UByte, value: UInt) {
this.writeUByte(tag) this.writeUByte(tag)
this.writeUVarInt(value) this.writeUVarInt(value)
} }
internal fun BytePacketBuilder.writeTByteArray(tag: UByte, value: ByteArray) { fun BytePacketBuilder.writeTByteArray(tag: UByte, value: ByteArray) {
this.writeUByte(tag) this.writeUByte(tag)
this.writeFully(value) this.writeFully(value)
} }
internal fun BytePacketBuilder.writeTByteArray(tag: UByte, value: UByteArray) { fun BytePacketBuilder.writeTByteArray(tag: UByte, value: UByteArray) {
this.writeUByte(tag) this.writeUByte(tag)
this.writeFully(value) this.writeFully(value)
} }
...@@ -113,43 +111,18 @@ internal fun BytePacketBuilder.writeTByteArray(tag: UByte, value: UByteArray) { ...@@ -113,43 +111,18 @@ internal fun BytePacketBuilder.writeTByteArray(tag: UByte, value: UByteArray) {
/** /**
* 会使用 [ByteArrayPool] 缓存 * 会使用 [ByteArrayPool] 缓存
*/ */
internal inline fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) = inline fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) =
BytePacketBuilder().apply(encoder).build().encryptBy(key) { decrypted -> writeFully(decrypted) } BytePacketBuilder().apply(encoder).build().encryptBy(key) { decrypted -> writeFully(decrypted) }
internal inline fun BytePacketBuilder.encryptAndWrite(key: IoBuffer, encoder: BytePacketBuilder.() -> Unit) = ByteArrayPool.useInstance { inline fun BytePacketBuilder.encryptAndWrite(key: IoBuffer, encoder: BytePacketBuilder.() -> Unit) = ByteArrayPool.useInstance {
key.readFully(it, 0, key.readRemaining) key.readFully(it, 0, key.readRemaining)
encryptAndWrite(it, encoder) encryptAndWrite(it, encoder)
} }
internal inline fun BytePacketBuilder.encryptAndWrite(key: DecrypterByteArray, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(key.value, encoder) inline fun BytePacketBuilder.encryptAndWrite(keyHex: String, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(keyHex.hexToBytes(), encoder)
internal inline fun BytePacketBuilder.encryptAndWrite(keyHex: String, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(keyHex.hexToBytes(), encoder)
internal fun BytePacketBuilder.writeTLV0006(qq: UInt, password: String, loginTime: Int, loginIP: String, privateKey: PrivateKey) {
val firstMD5 = md5(password)
val secondMD5 = md5(firstMD5 + byteArrayOf(0, 0, 0, 0) + qq.toUInt().toByteArray())
this.encryptAndWrite(secondMD5) {
writeRandom(4)
writeHex("00 02")
writeQQ(qq)
writeFully(TIMProtocol.constantData2)
writeHex("00 00 01")
writeFully(firstMD5)
writeInt(loginTime)
writeByte(0)
writeZero(4 * 3)
writeIP(loginIP)
writeZero(8)
writeHex("00 10")//这两个hex是passwordSubmissionTLV2的末尾
writeHex("15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B")//16
writeFully(privateKey.value)
}
}
@Tested @Tested
internal fun BytePacketBuilder.writeDeviceName(random: Boolean) { fun BytePacketBuilder.writeDeviceName(random: Boolean) {
val deviceName: String = if (random) { val deviceName: String = if (random) {
"DESKTOP-" + String(ByteArray(7) { "DESKTOP-" + String(ByteArray(7) {
(if (Random.nextBoolean()) Random.nextInt('A'.toInt()..'Z'.toInt()) (if (Random.nextBoolean()) Random.nextInt('A'.toInt()..'Z'.toInt())
......
...@@ -23,9 +23,9 @@ expect class ClosedChannelException : IOException ...@@ -23,9 +23,9 @@ expect class ClosedChannelException : IOException
/** /**
* 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误. * 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误.
*/ */
internal class SendPacketInternalException(cause: Throwable?) : Exception(cause) class SendPacketInternalException(cause: Throwable?) : Exception(cause)
/** /**
* 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误. * 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误.
*/ */
internal class ReadPacketInternalException(cause: Throwable?) : Exception(cause) class ReadPacketInternalException(cause: Throwable?) : Exception(cause)
\ No newline at end of file \ No newline at end of file
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
package net.mamoe.mirai.utils.io package net.mamoe.mirai.utils.io
import kotlinx.io.core.IoBuffer import kotlinx.io.core.IoBuffer
import kotlinx.io.core.writeFully
import kotlinx.io.pool.ObjectPool import kotlinx.io.pool.ObjectPool
import kotlin.random.Random import kotlin.random.Random
import kotlin.random.nextInt import kotlin.random.nextInt
...@@ -105,8 +104,7 @@ fun String.hexToUBytes(): UByteArray = ...@@ -105,8 +104,7 @@ fun String.hexToUBytes(): UByteArray =
/** /**
* 生成长度为 [length], 元素为随机 `0..255` 的 [ByteArray] * 生成长度为 [length], 元素为随机 `0..255` 的 [ByteArray]
*/ */
@PublishedApi fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() }
internal fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() }
/** /**
* 随机生成长度为 [length] 的 [String]. * 随机生成长度为 [length] 的 [String].
...@@ -142,4 +140,4 @@ fun ByteArray.toUShort(): UShort = ...@@ -142,4 +140,4 @@ fun ByteArray.toUShort(): UShort =
* 从 [IoBuffer.Pool] [borrow][ObjectPool.borrow] 一个 [IoBuffer] 然后将 [this] 写入. * 从 [IoBuffer.Pool] [borrow][ObjectPool.borrow] 一个 [IoBuffer] 然后将 [this] 写入.
* 注意回收 ([ObjectPool.recycle]) * 注意回收 ([ObjectPool.recycle])
*/ */
fun ByteArray.toIoBuffer(): IoBuffer = IoBuffer.Pool.borrow().let { it.writeFully(this); it } fun ByteArray.toIoBuffer(offset: Int = 0, length: Int = this.size - offset): IoBuffer = IoBuffer.Pool.borrow().let { it.writeFully(this, offset, length); it }
\ No newline at end of file \ No newline at end of file
...@@ -2,7 +2,6 @@ package net.mamoe.mirai.utils.io ...@@ -2,7 +2,6 @@ package net.mamoe.mirai.utils.io
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.asserter
class TypeConversionTest { class TypeConversionTest {
......
@file:Suppress("unused") @file:Suppress("unused")
package net.mamoe.mirai.network.protocol.timpc.packet.event package net.mamoe.mirai.message
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.io.core.Input import kotlinx.io.core.Input
import kotlinx.io.core.use import kotlinx.io.core.use
import kotlinx.io.streams.inputStream import kotlinx.io.streams.inputStream
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.message.* import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.toExternalImage import net.mamoe.mirai.utils.toExternalImage
...@@ -24,7 +25,7 @@ import javax.imageio.ImageIO ...@@ -24,7 +25,7 @@ import javax.imageio.ImageIO
* JVM 平台相关扩展 * JVM 平台相关扩展
*/ */
@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: BufferedImage): Image = subject.uploadImage(image) suspend inline fun uploadImage(image: BufferedImage): Image = subject.uploadImage(image)
suspend inline fun uploadImage(image: URL): Image = subject.uploadImage(image) suspend inline fun uploadImage(image: 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)
......
...@@ -6,7 +6,8 @@ import kotlinx.coroutines.Dispatchers ...@@ -6,7 +6,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.io.core.Input import kotlinx.io.core.Input
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.network.protocol.timpc.packet.action.OverFileSizeMaxException import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.utils.OverFileSizeMaxException
import net.mamoe.mirai.utils.sendTo import net.mamoe.mirai.utils.sendTo
import net.mamoe.mirai.utils.toExternalImage import net.mamoe.mirai.utils.toExternalImage
import net.mamoe.mirai.utils.upload import net.mamoe.mirai.utils.upload
......
@file:Suppress("EXPERIMENTAL_API_USAGE") @file:Suppress("EXPERIMENTAL_API_USAGE", "unused")
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import io.ktor.util.asStream import io.ktor.util.asStream
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.io.jvm.javaio.copyTo
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.io.core.Input import kotlinx.io.core.Input
import kotlinx.io.core.IoBuffer import kotlinx.io.core.IoBuffer
...@@ -48,8 +47,7 @@ fun BufferedImage.toExternalImage(formatName: String = "gif"): ExternalImage { ...@@ -48,8 +47,7 @@ fun BufferedImage.toExternalImage(formatName: String = "gif"): ExternalImage {
return ExternalImage(width, height, digest.digest(), formatName, buffer) return ExternalImage(width, height, digest.digest(), formatName, buffer)
} }
@Suppress("unused") suspend inline fun BufferedImage.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
suspend fun BufferedImage.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
/** /**
* 读取文件头识别图片属性, 然后构造 [ExternalImage] * 读取文件头识别图片属性, 然后构造 [ExternalImage]
...@@ -75,8 +73,7 @@ fun File.toExternalImage(): ExternalImage { ...@@ -75,8 +73,7 @@ fun File.toExternalImage(): ExternalImage {
/** /**
* 在 [IO] 中进行 [File.toExternalImage] * 在 [IO] 中进行 [File.toExternalImage]
*/ */
@Suppress("unused") suspend inline fun File.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
suspend fun File.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
/** /**
* 下载文件到临时目录然后调用 [File.toExternalImage] * 下载文件到临时目录然后调用 [File.toExternalImage]
...@@ -95,8 +92,7 @@ fun URL.toExternalImage(): ExternalImage { ...@@ -95,8 +92,7 @@ fun URL.toExternalImage(): ExternalImage {
/** /**
* 在 [IO] 中进行 [URL.toExternalImage] * 在 [IO] 中进行 [URL.toExternalImage]
*/ */
@Suppress("unused") suspend inline fun URL.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
suspend fun URL.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
/** /**
* 保存为临时文件然后调用 [File.toExternalImage] * 保存为临时文件然后调用 [File.toExternalImage]
...@@ -114,11 +110,12 @@ fun InputStream.toExternalImage(): ExternalImage { ...@@ -114,11 +110,12 @@ fun InputStream.toExternalImage(): ExternalImage {
/** /**
* 在 [IO] 中进行 [InputStream.toExternalImage] * 在 [IO] 中进行 [InputStream.toExternalImage]
*/ */
@Suppress("unused") suspend inline fun InputStream.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
suspend fun InputStream.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
/** /**
* 保存为临时文件然后调用 [File.toExternalImage] * 保存为临时文件然后调用 [File.toExternalImage].
*
* 需要函数调用者 close [this]
*/ */
@Throws(IOException::class) @Throws(IOException::class)
fun Input.toExternalImage(): ExternalImage { fun Input.toExternalImage(): ExternalImage {
...@@ -132,5 +129,4 @@ fun Input.toExternalImage(): ExternalImage { ...@@ -132,5 +129,4 @@ fun Input.toExternalImage(): ExternalImage {
/** /**
* 在 [IO] 中进行 [Input.toExternalImage] * 在 [IO] 中进行 [Input.toExternalImage]
*/ */
@Suppress("unused") suspend inline fun Input.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
suspend fun Input.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
int __fastcall sub_F74(int a1, int a2, int a3, signed int a4, int a5, const char *a6)
{
int v6; // r4
int v7; // r5
int v8; // r7
int v9; // r0
char *v10; // r6
int v11; // r0
int v12; // r0
signed int v13; // r7
int v14; // r0
int v15; // r5
signed int v16; // r5
int v17; // r0
int v18; // r5
int v19; // r7
int v20; // r5
int v21; // r0
signed int v22; // r5
signed int v23; // r7
int v24; // r0
int v25; // r0
int v26; // r0
int v27; // r0
int v28; // r3
signed int v29; // r0
int v30; // r2
int result; // r0
int v32; // r2
int v33; // r2
int v34; // [sp+0h] [bp-10D0h]
int v35; // [sp+4h] [bp-10CCh]
signed int v36; // [sp+8h] [bp-10C8h]
int v37; // [sp+Ch] [bp-10C4h]
const char *v38; // [sp+10h] [bp-10C0h]
int v39; // [sp+14h] [bp-10BCh]
int v40; // [sp+18h] [bp-10B8h]
int v41; // [sp+1Ch] [bp-10B4h]
int v42; // [sp+20h] [bp-10B0h]
_DWORD *v43; // [sp+24h] [bp-10ACh]
int v44; // [sp+28h] [bp-10A8h]
int v45; // [sp+2Ch] [bp-10A4h]
int v46; // [sp+30h] [bp-10A0h]
char v47; // [sp+34h] [bp-109Ch]
char v48; // [sp+B4h] [bp-101Ch]
char v49; // [sp+2B4h] [bp-E1Ch]
int v50; // [sp+4B4h] [bp-C1Ch]
char v51; // [sp+8B4h] [bp-81Ch]
unsigned __int8 v52; // [sp+CB4h] [bp-41Ch]
int v53; // [sp+10B4h] [bp-1Ch]
v6 = a2;
v36 = a4;
v40 = a3;
v37 = a5;
v7 = (int)&_stack_chk_guard;
v8 = 676;
v38 = a6;
v41 = a1;
v9 = (*(int (__fastcall **)(int, signed int, _DWORD))(*(_DWORD *)a2 + 676))(a2, a4, 0);
v43 = &_stack_chk_guard;
v10 = (char *)v9;
v39 = 676;
if ( v9 )
{
j_memset(&v50, 0, 1024);
v8 = (int)"%s";
j_snprintf(&v50, 1024, "%s", v10);
v7 = 680;
(*(void (__fastcall **)(int, signed int, char *))(*(_DWORD *)v6 + 680))(v6, v36, v10);
v10 = &aJavaOicqWlogin_1[(_DWORD)&v42 + 27];
j_memset(&aJavaOicqWlogin_1[(_DWORD)&v42 + 27], 0, 1024);
v36 = 680;
if ( v41 )
{
v11 = (*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)v6 + 676))(v6, a5, 0);
v7 = v11;
if ( !v11 )
{
v29 = 3;
goto LABEL_59;
}
j_snprintf(&aJavaOicqWlogin_1[(_DWORD)&v42 + 27], 1024, "%s", v11);
(*(void (__fastcall **)(int, int, int))(*(_DWORD *)v6 + 680))(v6, a5, v7);
}
v10 = (char *)&_stack_chk_fail + (_DWORD)&v42;
j_memset((char *)&_stack_chk_fail + (_DWORD)&v42, 0, 1024);
if ( v41 )
{
v12 = (*(int (__fastcall **)(int, const char *, _DWORD))(*(_DWORD *)v6 + 676))(v6, a6, 0);
v7 = v12;
if ( !v12 )
{
v29 = 2;
goto LABEL_59;
}
j_snprintf((char *)&_stack_chk_fail + (_DWORD)&v42, 1024, "%s", v12);
(*(void (__fastcall **)(int, const char *, int))(*(_DWORD *)v6 + 680))(v6, a6, v7);
}
j_memset(&v47, 0, 128);
v44 = 512;
v37 = j_EC_KEY_new_by_curve_name(711);
if ( !v37 )
{
j___android_log_print(4, "wlogin_sdk", "ERROR:EC_KEY_new_by_curve_name failed.");
v8 = -1;
LABEL_56:
j___android_log_print(4, "wlogin_sdk", "GenerateKey failed peerBase16PublicKey %s ret %d", &v50, v8, v35);
v29 = 4;
goto LABEL_59;
}
v7 = v52;
if ( !v52 )
{
if ( j_EC_KEY_generate_key(v37) != 1 )
{
j___android_log_print(4, "wlogin_sdk", "EC_KEY_generate_key failed ret %d");
v10 = (_BYTE *)(&stru_3F8 + 8);
v36 = 128;
v38 = (_BYTE *)(&stru_3F8 + 8);
v13 = 2;
LABEL_51:
v8 = -v13;
goto LABEL_53;
}
LABEL_35:
v39 = j_EC_KEY_get0_group(v37);
if ( v39 )
{
v24 = j_EC_KEY_get0_public_key(v37);
v7 = v24;
if ( !v24 )
{
j___android_log_print(4, "wlogin_sdk", "ERROR:EC_KEY_get0_public_key failed");
v10 = (_BYTE *)(&stru_3F8 + 8);
v36 = 128;
v38 = (_BYTE *)(&stru_3F8 + 8);
v13 = 5;
goto LABEL_51;
}
v38 = (const char *)j_EC_POINT_point2oct(v39, v24, 2, &aJavaOicqWlogin_1[(_DWORD)&v42 + 27], 67, 0);
if ( (signed int)v38 > 0 )
{
v25 = j_EC_KEY_get0_private_key(v37);
v7 = v25;
if ( v25 )
{
v26 = j_BN_bn2mpi(v25, 0);
v10 = (char *)v26;
if ( v26 > 1024 )
{
j___android_log_print(4, "wlogin_sdk", "ERROR:privateKeyLen %d larger than buff len %d", v26, 1024);
v10 = (_BYTE *)(&stru_3F8 + 8);
v23 = 5;
v36 = 128;
goto LABEL_45;
}
j_BN_bn2mpi(v7, (char *)&_stack_chk_fail + (_DWORD)&v42);
v27 = j_strlen(&v50);
String2Buffer(&v50, v27, &v48, &v44);
v7 = j_EC_POINT_new(v39);
if ( j_EC_POINT_oct2point(v39, v7, &v48, v44, 0) != 1 )
{
j___android_log_print(4, "wlogin_sdk", "EC_POINT_oct2point failed %d");
v13 = 6;
v36 = 128;
goto LABEL_51;
}
v28 = j_ECDH_compute_key(&v49, 512, v7, v37, 0);
if ( v28 > 0 )
{
j_MD5(&v49, v28, &v47);
v8 = 0;
v36 = 16;
LABEL_53:
j_EC_KEY_free(v37);
if ( v7 )
j_EC_POINT_free(v7);
if ( !v8 )
{
v37 = (*(int (__fastcall **)(int, int))(*(_DWORD *)v6 + 124))(v6, v40);
v7 = (*(int (__fastcall **)(int, const char *))(*(_DWORD *)v6 + 704))(v6, v38);
(*(void (__fastcall **)(int, int, _DWORD, const char *, char *, int))(*(_DWORD *)v6 + 832))(
v6,
v7,
0,
v38,
&aJavaOicqWlogin_1[(_DWORD)&v42 + 27],
v35);
v38 = "([B)V";
v39 = 704;
v30 = (*(int (__fastcall **)(int, int, const char *, const char *))(*(_DWORD *)v6 + 132))(
v6,
v37,
"set_c_pub_key",
"([B)V");
v42 = 832;
if ( v30 )
goto LABEL_61;
v29 = 5;
goto LABEL_59;
}
goto LABEL_56;
}
j___android_log_print(4, "wlogin_sdk", "ERROR:Gene ShareKey failed: %d", v28);
v36 = 128;
}
else
{
j___android_log_print(4, "wlogin_sdk", "ERROR:EC_KEY_get0_private_key failed.");
v10 = (_BYTE *)(&stru_3F8 + 8);
v36 = 128;
}
v13 = 7;
goto LABEL_51;
}
j___android_log_print(4, "wlogin_sdk", "ERROR:EC_POINT_point2oct failed, pubkey len:%d.", v38);
v36 = 128;
v38 = (_BYTE *)(&stru_3F8 + 8);
v10 = (_BYTE *)(&stru_3F8 + 8);
v23 = 6;
}
else
{
j___android_log_print(4, "wlogin_sdk", "ERROR:EC_KEY_get0_group failed");
v10 = (_BYTE *)(&stru_3F8 + 8);
v36 = 128;
v38 = (_BYTE *)(&stru_3F8 + 8);
v23 = 4;
}
LABEL_45:
v8 = -v23;
v7 = 0;
goto LABEL_53;
}
v45 = 512;
v14 = j_strlen((char *)&_stack_chk_fail + (_DWORD)&v42);
String2Buffer((char *)&_stack_chk_fail + (_DWORD)&v42, v14, &v48, &v45);
v15 = j_BN_mpi2bn(&v48, v45, 0);
if ( !v15 )
{
j___android_log_print(4, "wlogin_sdk", "BN_mpi2bn failed");
v16 = 1;
LABEL_24:
v20 = -v16;
goto LABEL_34;
}
if ( j_EC_KEY_set_private_key(v37, v15) != 1 )
{
j___android_log_print(4, "wlogin_sdk", "EC_KEY_set_private_key failed %d");
j_BN_free(v15);
v16 = 2;
goto LABEL_24;
}
j_BN_free(v15);
v17 = j_EC_KEY_get0_group(v37);
v18 = v17;
if ( !v17 )
{
j___android_log_print(4, "wlogin_sdk", "EC_KEY_get0_group failed");
v16 = 3;
goto LABEL_24;
}
v19 = j_EC_POINT_new(v17);
if ( !v19 )
{
j___android_log_print(4, "wlogin_sdk", "EC_POINT_new failed");
v16 = 4;
goto LABEL_24;
}
if ( v51 )
{
v46 = 512;
v21 = j_strlen(&aJavaOicqWlogin_1[(_DWORD)&v42 + 27]);
String2Buffer(&aJavaOicqWlogin_1[(_DWORD)&v42 + 27], v21, &v49, &v46);
if ( j_EC_POINT_oct2point(v18, v19, &v49, v46, 0) != 1 )
{
j___android_log_print(4, "wlogin_sdk", "EC_POINT_oct2point failed ret %d");
v22 = 5;
goto LABEL_32;
}
}
else
{
v34 = 0;
v35 = 0;
if ( j_EC_POINT_mul(v18, v19, 0) != 1 )
{
j___android_log_print(4, "wlogin_sdk", "EC_POINT_mul failed ret %d");
v22 = 6;
goto LABEL_32;
}
}
v20 = 0;
if ( j_EC_KEY_set_public_key(v37, v19) == 1 )
goto LABEL_33;
j___android_log_print(4, "wlogin_sdk", "EC_KEY_set_public_key failed ret %d");
v22 = 7;
LABEL_32:
v20 = -v22;
LABEL_33:
j_EC_POINT_free(v19);
if ( !v20 )
goto LABEL_35;
LABEL_34:
j___android_log_print(4, "wlogin_sdk", "setECKey failed ret %d", v20, v34, v35);
v10 = (_BYTE *)(&stru_3F8 + 8);
v36 = 128;
v38 = (_BYTE *)(&stru_3F8 + 8);
v23 = 3;
goto LABEL_45;
}
v29 = 1;
LABEL_59:
result = -v29;
while ( v53 != *v43 )
{
LABEL_61:
(*(void (__fastcall **)(int, int))(*(_DWORD *)v6 + 244))(v6, v40);
(*(void (__fastcall **)(int, int))(*(_DWORD *)v6 + 92))(v6, v7);
if ( v41 )
{
v7 = (*(int (__fastcall **)(int, char *))(*(_DWORD *)v6 + v39))(v6, v10);
(*(void (__fastcall **)(int, int, int, char *, char *))(*(_DWORD *)v6 + v42))(
v6,
v7,
v8,
v10,
(char *)&_stack_chk_fail + (_DWORD)&v42);
v8 = *(_DWORD *)(*(_DWORD *)v6 + 132);
v32 = ((int (__fastcall *)(int, int, const char *, const char *))v8)(v6, v37, "set_c_pri_key", v38);
if ( !v32 )
{
v29 = 7;
goto LABEL_59;
}
v8 = *(_DWORD *)(*(_DWORD *)v6 + 244);
((void (__fastcall *)(int, int, int, int))v8)(v6, v40, v32, v7);
(*(void (__fastcall **)(int, int))(*(_DWORD *)v6 + 92))(v6, v7);
}
v7 = (*(int (__fastcall **)(int, signed int))(*(_DWORD *)v6 + v39))(v6, v36);
(*(void (__fastcall **)(int, int, _DWORD, signed int, char *))(*(_DWORD *)v6 + v42))(v6, v7, 0, v36, &v47);
v10 = *(char **)(*(_DWORD *)v6 + 132);
v33 = ((int (__fastcall *)(int, int, const char *, const char *))v10)(v6, v37, "set_g_share_key", "([B)V");
if ( !v33 )
{
v29 = 8;
goto LABEL_59;
}
v10 = *(char **)(*(_DWORD *)v6 + 244);
((void (__fastcall *)(int, int, int, int))v10)(v6, v40, v33, v7);
(*(void (__fastcall **)(int, int))(*(_DWORD *)v6 + 92))(v6, v7);
result = 0;
}
return result;
}
\ No newline at end of file
...@@ -2,13 +2,12 @@ package net.mamoe.mirai.contact ...@@ -2,13 +2,12 @@ package net.mamoe.mirai.contact
import net.mamoe.mirai.test.shouldBeEqualTo import net.mamoe.mirai.test.shouldBeEqualTo
import org.junit.Test import org.junit.Test
import kotlin.random.Random
@UseExperimental(ExperimentalUnsignedTypes::class) @UseExperimental(ExperimentalUnsignedTypes::class)
internal class GroupIdConversionsKtTest { internal class GroupIdConversionsKtTest {
@Test @Test
fun checkToInternalId() { fun checkToInternalId() {
GroupId(221056495u).toInternalId().value shouldBeEqualTo 4111056495u GroupId(221056495).toInternalId().value shouldBeEqualTo 4111056495
// 61 056495 // 61 056495
//4111 056495 //4111 056495
} }
......
...@@ -35,12 +35,14 @@ kotlin { ...@@ -35,12 +35,14 @@ kotlin {
} }
} }
fun DependencyHandlerScope.kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version" fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version"
fun DependencyHandlerScope.ktor(id: String, version: String) = "io.ktor:ktor-$id:$version" fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version"
dependencies { dependencies {
implementation(project(":mirai-core"))
implementation(files("../mirai-core-timpc/build/classes/kotlin/jvm/main")) // IDE bug
implementation(project(":mirai-core-timpc"))
// runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // classpath is not added correctly by IDE // runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // classpath is not added correctly by IDE
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
......
@file:Suppress("ObjectPropertyName", "unused", "NonAsciiCharacters", "MayBeConstant") @file:Suppress("ObjectPropertyName", "MayBeConstant", "NonAsciiCharacters", "SpellCheckingInspection", "unused", "EXPERIMENTAL_UNSIGNED_LITERALS")
package hex package hex
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.internal.printCompareHex import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.readUVarInt
@UseExperimental(MiraiInternalAPI::class) import net.mamoe.mirai.utils.io.toUHexString
fun main() { import kotlin.math.max
// println(HexComparator.printColorize("00 00 00 25 00 08 00 02 00 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 01 38 03 3E 03 3F A2 76 E4 B8 DD E7 86 74 F2 64 55 AD 9A EB 2F B9 DF F1 7F 8C 28 00 0B 78 14 5D A2 F5 CB 01 1D 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00 5D A2 F5 CA 9D 26 CB 5E 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E4 BD A0 E5 A5 BD 0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00")) import kotlin.reflect.KProperty0
while (true) {
println("Hex1: ") /**
val hex1 = readLine()!! * 匹配已知 hex 常量并格式化后打印到控制台.
println("Hex2: ") *
val hex2 = readLine()!! * 低效率, 仅调试使用.
println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n") */
println(printCompareHex(hex1.toUpperCase(), hex2.toUpperCase())) internal fun String.printColorize(ignoreUntilFirstConst: Boolean): String = with(HexComparator) { colorize(ignoreUntilFirstConst) }
println()
/**
* 比较两个 hex 并格式化后打印到控制台.
*
* 低效率, 仅调试使用.
*/
@MiraiInternalAPI
fun printCompareHex(hex1s: String, hex2s: String): String = with(HexComparator) {
compare(
hex1s.toUpperCase(),
hex2s.toUpperCase()
)
}
data class NamedHexElement(
val name: String,
val value: String
)
/**
* 初始化用于匹配的 Hex 列表
*/
private fun LinkedHashSet<NamedHexElement>.initConstFileds() {
listOf(
TestConsts,
TIMProtocol,
PacketIds
).forEach { obj ->
obj::class.members.filterIsInstance<KProperty0<*>>().forEach { property ->
property.get()?.let { add(NamedHexElement(property.name, it.toString())) }
}
}
}
private object TestConsts {
val NIU_BI = "牛逼".toByteArray().toUHexString()
val _1994701021 = 1994701021u.toUHexString(" ")
val _1040400290 = 1040400290u.toUHexString(" ")
val _580266363 = 580266363u.toUHexString(" ")
val _761025446 = 761025446u.toUHexString()
val _1040400290_ = "3E 03 3F A2"
val _1994701021_ = "76 E4 B8 DD"
val _jiahua_ = "B1 89 BE 09"
val _Him188moe_ = "Him188moe".toByteArray().toUHexString()
val 发图片2 = "发图片2".toByteArray().toUHexString()
val 发图片群 = "发图片群".toByteArray().toUHexString()
val 发图片 = "发图片".toByteArray().toUHexString()
val 群 = "群".toByteArray().toUHexString()
val 你好 = "你好".toByteArray().toUHexString()
val MESSAGE_TAIL_10404 =
"0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00"
.replace(" ", " ")
val FONT_10404 = "E5 BE AE E8 BD AF E9 9B 85 E9 BB 91"
val varint580266363 = "FB D2 D8 94 02"
val varint1040400290 = "A2 FF 8C F0 03"
val varint1994701021 = "DD F1 92 B7 07"
val varint761025446 = 761025446u.toUHexString().hexToBytes().read { readUVarInt() }.toUHexString()
}
@Suppress("SpellCheckingInspection")
private object PacketIds {
val heartbeat = "00 58"
val friendmsgsend = "00 CD"
val friendmsgevent = "00 CE"
val groupmsg = "00 02"
}
/**
* Hex 比较器, 并着色已知常量
*
* This could be used to check packet encoding..
* but better to run under UNIX
*
* @author NaturalHG
* @author Him188moe
*/
private object HexComparator {
private val RED = "\u001b[31m"
private val GREEN = "\u001b[33m"
private val UNKNOWN_COLOR = "\u001b[30m"
private val BLUE = "\u001b[34m"
@Suppress("unused")
private class ConstMatcher constructor(hex: String) {
private val matches = linkedSetOf<Match>()
fun getMatchedConstName(hexNumber: Int): String? {
for (match in this.matches) {
if (match.range.contains(hexNumber)) {
return match.constName
}
}
return null
}
private class Match internal constructor(val range: IntRange, val constName: String)
init {
CONST_FIELDS.forEach { (name, value) ->
for (match in match(hex, value)) {
matches.add(Match(match, name))
}
}
}
companion object {
val CONST_FIELDS: MutableSet<NamedHexElement> = linkedSetOf<NamedHexElement>().apply { initConstFileds() }
}
private fun match(hex: String, value: String): Set<IntRange> {
val constValue: String
try {
constValue = value.trim { it <= ' ' }
if (constValue.length / 3 <= 3) {//Minimum numbers of const hex bytes
return linkedSetOf()
}
} catch (ignored: ClassCastException) {
return linkedSetOf()
}
return mutableSetOf<IntRange>().apply {
var index = -1
index = hex.indexOf(constValue, index + 1)
while (index != -1) {
add(IntRange(index / 3, (index + constValue.length) / 3))
index = hex.indexOf(constValue, index + 1)
}
}
}
}
private fun buildConstNameChain(length: Int, constMatcher: ConstMatcher, constNameBuilder: StringBuilder) {
//System.out.println(constMatcher.matches);
var i = 0
while (i < length) {
constNameBuilder.append(" ")
val match = constMatcher.getMatchedConstName(i / 4)
if (match != null) {
var appendedNameLength = match.length
constNameBuilder.append(match)
while (match == constMatcher.getMatchedConstName(i++ / 4)) {
if (appendedNameLength-- < 0) {
constNameBuilder.append(" ")
}
}
constNameBuilder.append(" ".repeat(match.length % 4))
}
i++
}
}
fun compare(hex1s: String, hex2s: String): String {
val builder = StringBuilder()
val hex1 = hex1s.trim { it <= ' ' }.replace("\n", "").split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val hex2 = hex2s.trim { it <= ' ' }.replace("\n", "").split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val constMatcher1 = ConstMatcher(hex1s)
val constMatcher2 = ConstMatcher(hex2s)
if (hex1.size == hex2.size) {
builder.append(GREEN).append("长度一致:").append(hex1.size)
} else {
builder.append(RED).append("长度不一致").append(hex1.size).append("/").append(hex2.size)
}
val numberLine = StringBuilder()
val hex1ConstName = StringBuilder()
val hex1b = StringBuilder()
val hex2b = StringBuilder()
val hex2ConstName = StringBuilder()
var dif = 0
val length = max(hex1.size, hex2.size) * 4
buildConstNameChain(length, constMatcher1, hex1ConstName)
buildConstNameChain(length, constMatcher2, hex2ConstName)
for (i in 0 until max(hex1.size, hex2.size)) {
var h1: String? = null
var h2: String? = null
var isDif = false
if (hex1.size <= i) {
h1 = RED + "__"
isDif = true
} else {
val matchedConstName = constMatcher1.getMatchedConstName(i)
if (matchedConstName != null) {
h1 = BLUE + hex1[i]
}
}
if (hex2.size <= i) {
h2 = RED + "__"
isDif = true
} else {
val matchedConstName = constMatcher2.getMatchedConstName(i)
if (matchedConstName != null) {
h2 = BLUE + hex2[i]
}
}
if (h1 == null && h2 == null) {
h1 = hex1[i]
h2 = hex2[i]
if (h1 == h2) {
h1 = GREEN + h1
h2 = GREEN + h2
} else {
h1 = RED + h1
h2 = RED + h2
isDif = true
}
} else {
if (h1 == null) {
h1 = RED + hex1[i]
}
if (h2 == null) {
h2 = RED + hex2[i]
}
}
numberLine.append(UNKNOWN_COLOR).append(getFixedNumber(i)).append(" ")
hex1b.append(" ").append(h1).append(" ")
hex2b.append(" ").append(h2).append(" ")
if (isDif) {
++dif
}
//doConstReplacement(hex1b);
//doConstReplacement(hex2b);
}
return builder.append(" ").append(dif).append(" 个不同").append("\n")
.append(numberLine).append("\n")
.append(hex1ConstName).append("\n")
.append(hex1b).append("\n")
.append(hex2b).append("\n")
.append(hex2ConstName).append("\n")
.toString()
} }
/*
System.out.println(HexComparator.compare( fun String.colorize(ignoreUntilFirstConst: Boolean = false): String {
//mirai val builder = StringBuilder()
"2A 22 96 29 7B 00 40 00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 EC 21 40 06 18 89 54 BC Protocol.messageConst1 00 00 01 00 0A 01 00 07 E7 89 9B E9 80 BC 21\n" val hex = trim { it <= ' ' }.replace("\n", "").split(" ").dropLastWhile { it.isEmpty() }.toTypedArray()
, val constMatcher1 = ConstMatcher(this)
//e
"2A 22 96 29 7B 00 3F 00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 5D 6B 8E 1A FE 39 0B FC Protocol.messageConst1 00 00 01 00 0A 01 00 07 6D 65 73 73 61 67 65" val numberLine = StringBuilder()
)); val hex1ConstName = StringBuilder()
*/ val hex1b = StringBuilder()
buildConstNameChain(length, constMatcher1, hex1ConstName)
/*
System.out.println(HexComparator.compare( var firstConst: String? = null
//e var constNameOffset = 0//已经因为还没到第一个const跳过了几个char
"90 5E 39 DF 00 02 76 E4 B8 DD 00 00 04 53 00 00 00 01 00 00 15 85 00 00 01 55 35 05 8E C9 BA 16 D0 01 63 5B 59 4B 59 52 31 01 B9 00 00 00 00 00 00 00 00 00 00 00 00 00 7B 7B 7B 7B 00 00 00 00 00 00 00 00 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B AA BB CC DD EE FF AA BB CC", for (i in hex.indices) {
//mirai var h1: String? = null
"6F 0B DF 92 00 02 76 E4 B8 DD 00 00 04 53 00 00 00 01 00 00 15 85 00 00 01 55 35 05 8E C9 BA 16 D0 01 63 5B 59 4B 59 52 31 01 B9 00 00 00 00 00 00 00 00 00 00 00 00 00 E9 E9 E9 E9 00 00 00 00 00 00 00 00 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B AA BB CC DD EE FF AA BB CC\n\n\n"
));*/ val matchedConstName = constMatcher1.getMatchedConstName(i)
if (matchedConstName != null) {
firstConst = firstConst ?: matchedConstName
h1 = BLUE + hex[i]
}
if (!ignoreUntilFirstConst || firstConst != null) {//有过任意一个 Const
if (h1 == null) {
h1 = GREEN + hex[i]
}
numberLine.append(UNKNOWN_COLOR).append(getFixedNumber(i)).append(" ")
hex1b.append(" ").append(h1).append(" ")
} else {
constNameOffset++
}
}
return builder.append("\n")
.append(numberLine).append("\n")
.append(if (firstConst == null) hex1ConstName else {
with(hex1ConstName) {
val index = indexOf(firstConst)
if (index == -1) toString() else " " + substring(index, length)
}
}).append("\n")
.append(hex1b).append("\n")
.toString()
}
private fun getFixedNumber(number: Int): String {
if (number < 10) {
return "00$number"
}
return if (number < 100) {
"0$number"
} else number.toString()
}
} }
\ No newline at end of file
package test package test
import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult import net.mamoe.mirai.network.data.LoginResult
@Suppress("RedundantSuspendModifier") @Suppress("RedundantSuspendModifier")
suspend fun suspendPrintln(arg: String) = println(arg) suspend fun suspendPrintln(arg: String) = println(arg)
......
...@@ -9,7 +9,6 @@ import net.mamoe.mirai.utils.io.toUHexString ...@@ -9,7 +9,6 @@ import net.mamoe.mirai.utils.io.toUHexString
@ExperimentalStdlibApi @ExperimentalStdlibApi
@Suppress("EXPERIMENTAL_API_USAGE") @Suppress("EXPERIMENTAL_API_USAGE")
fun main() { fun main() {
val newMap = val newMap =
"4E 22 00 03 E5 AE 89 4E 25 00 06 35 31 31 34 39 35 4E 26 00 01 2D 4E 27 00 01 2D 4E 29 00 01 02 4E 2A 00 06 56 69 76 69 61 6E 4E 2B 00 10 31 35 36 31 34 38 39 31 33 40 71 71 2E 63 6F 6D 4E 2D 00 1D 68 74 74 70 3A 2F 2F 31 35 36 31 34 38 39 31 33 2E 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 4E 2E 00 02 33 00 4E 2F 00 04 33 33 39 00 4E 30 00 01 2D 4E 31 00 01 00 4E 33 00 2D E6 88 91 E7 95 99 E9 95 BF E7 9A 84 E5 A4 B4 E5 8F 91 EF BC 8C E6 98 AF E4 BD A0 E9 94 99 E8 BF 87 E7 9A 84 E5 B9 B4 E5 8D 8E 2E 2E 2E 4E 35 00 18 E5 B9 BF E4 B8 9C E6 8A 80 E6 9C AF E5 B8 88 E8 8C 83 E5 AD A6 E9 99 A2 4E 36 00 01 0A 4E 37 00 01 03 4E 38 00 01 01 4E 3F 00 04 07 C2 0B 02 4E 40 00 0C 00 00 00 31 00 00 34 34 00 00 00 33 4E 41 00 02 00 00 4E 42 00 02 00 00 4E 43 00 02 00 00 4E 45 00 01 21 4E 49 00 04 00 00 00 00 4E 4B 00 04 00 00 00 00 4E 4F 00 01 00 4E 54 00 00 4E 5B 00 00 52 0B 00 04 13 88 02 02 52 0F 00 14 00 00 00 00 00 00 00 00 12 05 10 58 89 10 00 00 00 00 00 00 5D C2 00 0C 00 00 00 31 00 00 34 34 00 00 31 34 5D C8 00 1E E7 B4 A2 E5 B0 BC EF BC 88 E4 B8 AD E5 9B BD EF BC 89 E6 9C 89 E9 99 90 E5 85 AC E5 8F B8 65 97 00 01 11 69 9D 00 04 00 00 00 00 69 A9 00 00 9D A5 00 02 00 00 A4 91 00 02 00 00 A4 93 00 02 00 00 A4 94 00 02 00 00 A4 9C 00 02 00 00 A4 B5 00 02 00 00" "4E 22 00 03 E5 AE 89 4E 25 00 06 35 31 31 34 39 35 4E 26 00 01 2D 4E 27 00 01 2D 4E 29 00 01 02 4E 2A 00 06 56 69 76 69 61 6E 4E 2B 00 10 31 35 36 31 34 38 39 31 33 40 71 71 2E 63 6F 6D 4E 2D 00 1D 68 74 74 70 3A 2F 2F 31 35 36 31 34 38 39 31 33 2E 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 4E 2E 00 02 33 00 4E 2F 00 04 33 33 39 00 4E 30 00 01 2D 4E 31 00 01 00 4E 33 00 2D E6 88 91 E7 95 99 E9 95 BF E7 9A 84 E5 A4 B4 E5 8F 91 EF BC 8C E6 98 AF E4 BD A0 E9 94 99 E8 BF 87 E7 9A 84 E5 B9 B4 E5 8D 8E 2E 2E 2E 4E 35 00 18 E5 B9 BF E4 B8 9C E6 8A 80 E6 9C AF E5 B8 88 E8 8C 83 E5 AD A6 E9 99 A2 4E 36 00 01 0A 4E 37 00 01 03 4E 38 00 01 01 4E 3F 00 04 07 C2 0B 02 4E 40 00 0C 00 00 00 31 00 00 34 34 00 00 00 33 4E 41 00 02 00 00 4E 42 00 02 00 00 4E 43 00 02 00 00 4E 45 00 01 21 4E 49 00 04 00 00 00 00 4E 4B 00 04 00 00 00 00 4E 4F 00 01 00 4E 54 00 00 4E 5B 00 00 52 0B 00 04 13 88 02 02 52 0F 00 14 00 00 00 00 00 00 00 00 12 05 10 58 89 10 00 00 00 00 00 00 5D C2 00 0C 00 00 00 31 00 00 34 34 00 00 31 34 5D C8 00 1E E7 B4 A2 E5 B0 BC EF BC 88 E4 B8 AD E5 9B BD EF BC 89 E6 9C 89 E9 99 90 E5 85 AC E5 8F B8 65 97 00 01 11 69 9D 00 04 00 00 00 00 69 A9 00 00 9D A5 00 02 00 00 A4 91 00 02 00 00 A4 93 00 02 00 00 A4 94 00 02 00 00 A4 9C 00 02 00 00 A4 B5 00 02 00 00"
.hexToBytes().read { .hexToBytes().read {
......
...@@ -2,8 +2,10 @@ apply plugin: "kotlin" ...@@ -2,8 +2,10 @@ apply plugin: "kotlin"
apply plugin: "java" apply plugin: "java"
dependencies { dependencies {
api project(":mirai-core")
// runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // classpath is not set correctly by IDE implementation files("../../mirai-core-timpc/build/classes/kotlin/jvm/main") // IDE bug
implementation project(":mirai-core-timpc")
api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion
} }
...@@ -10,10 +10,14 @@ import net.mamoe.mirai.alsoLogin ...@@ -10,10 +10,14 @@ import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.sendMessage import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.event.* import net.mamoe.mirai.event.*
import net.mamoe.mirai.message.* import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.network.protocol.timpc.packet.action.uploadImage import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.network.protocol.timpc.packet.event.FriendMessage import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.network.protocol.timpc.packet.event.GroupMessage import net.mamoe.mirai.message.data.ImageId
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.firstOrNull
import net.mamoe.mirai.message.sendAsImageTo
import net.mamoe.mirai.timpc.TIMPC
import net.mamoe.mirai.utils.suspendToExternalImage import net.mamoe.mirai.utils.suspendToExternalImage
import java.io.File import java.io.File
...@@ -25,7 +29,7 @@ private fun readTestAccount(): BotAccount? { ...@@ -25,7 +29,7 @@ private fun readTestAccount(): BotAccount? {
val lines = file.readLines() val lines = file.readLines()
return try { return try {
BotAccount(lines[0].toUInt(), lines[1]) BotAccount(lines[0].toLong(), lines[1])
} catch (e: IndexOutOfBoundsException) { } catch (e: IndexOutOfBoundsException) {
null null
} }
...@@ -33,9 +37,9 @@ private fun readTestAccount(): BotAccount? { ...@@ -33,9 +37,9 @@ private fun readTestAccount(): BotAccount? {
@Suppress("UNUSED_VARIABLE") @Suppress("UNUSED_VARIABLE")
suspend fun main() { suspend fun main() {
val bot = Bot( val bot = TIMPC.Bot(
readTestAccount() ?: BotAccount(//填写你的账号 readTestAccount() ?: BotAccount(//填写你的账号
id = 1994701121u, id = 1994701121,
password = "123456" password = "123456"
) )
).alsoLogin { ).alsoLogin {
...@@ -211,37 +215,65 @@ suspend fun directlySubscribe(bot: Bot) { ...@@ -211,37 +215,65 @@ suspend fun directlySubscribe(bot: Bot) {
"复读" in it.message -> it.sender.sendMessage(it.message) "复读" in it.message -> it.sender.sendMessage(it.message)
"发群消息" in it.message -> 580266363u.group().sendMessage(it.message.toString().substringAfter("发群消息")) "发群消息" in it.message -> 580266363.group().sendMessage(it.message.toString().substringAfter("发群消息"))
"上传群图片" in it.message -> withTimeoutOrNull(5000) { "上传群图片" in it.message -> withTimeoutOrNull(5000) {
val filename = it.message.toString().substringAfter("上传群图片") val filename = it.message.toString().substringAfter("上传群图片")
val image = File( val image = File(
"C:\\Users\\Him18\\Desktop\\$filename" "C:\\Users\\Him18\\Desktop\\$filename"
).suspendToExternalImage() ).suspendToExternalImage()
920503456u.group().uploadImage(image) 920503456.group().uploadImage(image)
it.reply(image.groupImageId.value) it.reply(image.groupImageId.value)
delay(100) delay(100)
920503456u.group().sendMessage(Image(image.groupImageId)) 920503456.group().sendMessage(Image(image.groupImageId))
} }
"发群图片" in it.message -> { "发群图片" in it.message -> {
920503456u.group().sendMessage(Image(ImageId(it.message.toString().substringAfter("发群图片")))) 920503456.group().sendMessage(
Image(
ImageId(
it.message.toString().substringAfter(
"发群图片"
)
)
)
)
} }
"发好友图片" in it.message -> { "发好友图片" in it.message -> {
it.reply(Image(ImageId(it.message.toString().substringAfter("发好友图片")))) it.reply(
Image(
ImageId(
it.message.toString().substringAfter(
"发好友图片"
)
)
)
)
} }
/*it.event eq "发图片群" -> sendGroupMessage(Group(session.bot, 580266363), PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image -> /*it.event eq "发图片群" -> sendGroupMessage(Group(session.bot, 580266363), PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image ->
image.upload(session, Group(session.bot, 580266363)).of() image.upload(session, Group(session.bot, 580266363)).of()
})*/ })*/
it.message eq "发图片群2" -> 580266363u.group().sendMessage(Image(ImageId("{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg"))) it.message eq "发图片群2" -> 580266363.group().sendMessage(
Image(
ImageId(
"{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg"
)
)
)
/* it.event eq "发图片" -> sendFriendMessage(it.sentBy, PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image -> /* it.event eq "发图片" -> sendFriendMessage(it.sentBy, PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image ->
image.upload(session, it.sentBy).of() image.upload(session, it.sentBy).of()
})*/ })*/
it.message eq "发图片2" -> it.reply(PlainText("test") + Image(ImageId("{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg"))) it.message eq "发图片2" -> it.reply(
PlainText("test") + Image(
ImageId(
"{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg"
)
)
)
else -> { else -> {
} }
......
...@@ -40,7 +40,7 @@ kotlin { ...@@ -40,7 +40,7 @@ kotlin {
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib" implementation "org.jetbrains.kotlin:kotlin-stdlib"
implementation project(':mirai-core') implementation project(':mirai-core-timpc')
implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion
implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion
......
...@@ -95,7 +95,7 @@ class MainActivity : AppCompatActivity(),LoginCallback { ...@@ -95,7 +95,7 @@ class MainActivity : AppCompatActivity(),LoginCallback {
} }
binder?.setCallback(this) binder?.setCallback(this)
if (!needCaptcha){ if (!needCaptcha){
val qq = et_qq.text.toString().toUInt() val qq = et_qq.text.toString().toLong()
val pwd = et_pwd.text.toString() val pwd = et_pwd.text.toString()
binder?.startLogin(qq, pwd) binder?.startLogin(qq, pwd)
}else{ }else{
......
...@@ -12,13 +12,9 @@ import kotlinx.coroutines.GlobalScope ...@@ -12,13 +12,9 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.io.core.readBytes import kotlinx.io.core.readBytes
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.event.subscribeMessages import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.login import net.mamoe.mirai.network.data.LoginResult
import net.mamoe.mirai.message.Image import net.mamoe.mirai.timpc.TIMPC
import net.mamoe.mirai.network.protocol.timpc.packet.event.GroupMessage
import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
class MiraiService : Service() { class MiraiService : Service() {
...@@ -43,9 +39,9 @@ class MiraiService : Service() { ...@@ -43,9 +39,9 @@ class MiraiService : Service() {
} }
private fun login(qq: UInt, pwd: String) { private fun login(qq: Long, pwd: String) {
GlobalScope.launch { GlobalScope.launch {
mBot = Bot(qq, pwd).apply { mBot = TIMPC.Bot(qq, pwd).apply {
val loginResult = login { val loginResult = login {
captchaSolver = { captchaSolver = {
val bytes = it.readBytes() val bytes = it.readBytes()
...@@ -64,75 +60,12 @@ class MiraiService : Service() { ...@@ -64,75 +60,12 @@ class MiraiService : Service() {
mBot.subscribeMessages { mBot.subscribeMessages {
content({ true }) { always {
mCallback?.get()?.onMessage("收到来自${sender.id}的消息") mCallback?.get()?.onMessage("收到来自${sender.id}的消息")
} }
// 当接收到消息 == "你好" 时就回复 "你好!" // 当接收到消息 == "你好" 时就回复 "你好!"
"你好" reply "你好!" "你好" reply "你好!"
// 当消息 == "查看 subject" 时, 执行 lambda
case("查看 subject") {
if (subject is QQ) {
reply("消息主体为 QQ, 你在跟发私聊消息")
} else {
reply("消息主体为 Group, 你在群里发消息")
}
// 在回复的时候, 一般使用 subject 来作为回复对象.
// 因为当群消息时, subject 为这个群.
// 当好友消息时, subject 为这个好友.
// 所有在 MessagePacket(也就是此时的 this 指代的对象) 中实现的扩展方法, 如刚刚的 "reply", 都是以 subject 作为目标
}
// 当消息里面包含这个类型的消息时
has<Image> {
// this: MessagePacket
// message: MessageChain
// sender: QQ
// it: String (MessageChain.toString)
if (this is GroupMessage) {
//如果是群消息
// group: Group
this.group.sendMessage("你在一个群里")
// 等同于 reply("你在一个群里")
}
reply("图片, ID= ${message[Image].id}")//获取第一个 Image 类型的消息
reply(message)
}
"123" containsReply "你的消息里面包含 123"
// 当收到 "我的qq" 就执行 lambda 并回复 lambda 的返回值 String
"我的qq" reply { sender.id }
// 当消息前缀为 "我是" 时
startsWith("我是", removePrefix = true) {
// it: 删除了消息前缀 "我是" 后的消息
// 如一条消息为 "我是张三", 则此时的 it 为 "张三".
reply("你是$it")
}
// 当消息中包含 "复读" 时
contains("复读") {
reply(message)
}
// 自定义的 filter, filter 中 it 为转为 String 的消息.
// 也可以用任何能在处理时使用的变量, 如 subject, sender, message
content({ it.length == 3 }) {
reply("你发送了长度为 3 的消息")
}
} }
} }
...@@ -146,7 +79,7 @@ class MiraiService : Service() { ...@@ -146,7 +79,7 @@ class MiraiService : Service() {
inner class MiraiBinder : Binder() { inner class MiraiBinder : Binder() {
fun startLogin(qq: UInt, pwd: String) { fun startLogin(qq: Long, pwd: String) {
login(qq, pwd) login(qq, pwd)
} }
......
...@@ -3,8 +3,8 @@ apply plugin: "java" ...@@ -3,8 +3,8 @@ apply plugin: "java"
apply plugin: "application" apply plugin: "application"
dependencies { dependencies {
api project(":mirai-core") implementation files("../../mirai-core-timpc/build/classes/kotlin/jvm/main") // IDE bug
//runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // classpath is not set correctly by IDE implementation project(":mirai-core-timpc")
implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion
implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion
......
...@@ -3,7 +3,7 @@ package demo.gentleman ...@@ -3,7 +3,7 @@ package demo.gentleman
import com.alibaba.fastjson.JSON import com.alibaba.fastjson.JSON
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.message.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.uploadAsImage import net.mamoe.mirai.message.uploadAsImage
import org.jsoup.Jsoup import org.jsoup.Jsoup
......
...@@ -17,7 +17,7 @@ private const val IMAGE_BUFFER_CAPACITY: Int = 5 ...@@ -17,7 +17,7 @@ private const val IMAGE_BUFFER_CAPACITY: Int = 5
*/ */
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
object Gentlemen : MutableMap<UInt, Gentleman> by mutableMapOf() { object Gentlemen : MutableMap<Long, Gentleman> by mutableMapOf() {
fun provide(key: Contact): Gentleman = this.getOrPut(key.id) { Gentleman(key) } fun provide(key: Contact): Gentleman = this.getOrPut(key.id) { Gentleman(key) }
} }
......
...@@ -7,16 +7,22 @@ import kotlinx.coroutines.GlobalScope ...@@ -7,16 +7,22 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.mamoe.mirai.* import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.event.Subscribable import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.event.events.ReceiveFriendAddRequestEvent
import net.mamoe.mirai.event.subscribeAlways import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.event.subscribeGroupMessages import net.mamoe.mirai.event.subscribeGroupMessages
import net.mamoe.mirai.event.subscribeMessages import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.message.* import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.network.protocol.timpc.packet.event.FriendMessage import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.network.protocol.timpc.packet.event.GroupMessage import net.mamoe.mirai.message.data.At
import net.mamoe.mirai.network.protocol.timpc.packet.event.ReceiveFriendAddRequestEvent import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.buildXMLMessage
import net.mamoe.mirai.message.data.getValue
import net.mamoe.mirai.message.sendAsImageTo
import net.mamoe.mirai.timpc.TIMPC
import java.io.File import java.io.File
import java.util.* import java.util.*
import javax.swing.filechooser.FileSystemView import javax.swing.filechooser.FileSystemView
...@@ -30,7 +36,7 @@ private fun readTestAccount(): BotAccount? { ...@@ -30,7 +36,7 @@ private fun readTestAccount(): BotAccount? {
val lines = file.readLines() val lines = file.readLines()
return try { return try {
BotAccount(lines[0].toUInt(), lines[1]) BotAccount(lines[0].toLong(), lines[1])
} catch (e: Exception) { } catch (e: Exception) {
null null
} }
...@@ -38,9 +44,9 @@ private fun readTestAccount(): BotAccount? { ...@@ -38,9 +44,9 @@ private fun readTestAccount(): BotAccount? {
@Suppress("UNUSED_VARIABLE") @Suppress("UNUSED_VARIABLE")
suspend fun main() { suspend fun main() {
val bot = Bot( val bot = TIMPC.Bot(
readTestAccount() ?: BotAccount( readTestAccount() ?: BotAccount(
id = 913366033u, id = 913366033,
password = "a18260132383" password = "a18260132383"
) )
).alsoLogin() ).alsoLogin()
...@@ -86,7 +92,7 @@ suspend fun main() { ...@@ -86,7 +92,7 @@ suspend fun main() {
startsWith("profile", removePrefix = true) { startsWith("profile", removePrefix = true) {
val account = it.trim() val account = it.trim()
if (account.isNotEmpty()) { if (account.isNotEmpty()) {
account.toUInt().qq() account.toLong().qq()
} else { } else {
sender sender
}.queryProfile().toString().reply() }.queryProfile().toString().reply()
...@@ -168,7 +174,7 @@ suspend fun main() { ...@@ -168,7 +174,7 @@ suspend fun main() {
} }
startsWith("添加好友", removePrefix = true) { startsWith("添加好友", removePrefix = true) {
reply(bot.addFriend(it.toUInt()).toString()) reply(bot.addFriend(it.toLong()).toString())
} }
} }
......
...@@ -22,6 +22,7 @@ pluginManagement { ...@@ -22,6 +22,7 @@ pluginManagement {
rootProject.name = 'mirai' rootProject.name = 'mirai'
include(':mirai-core') include(':mirai-core')
include(':mirai-core-timpc')
include(':mirai-console') include(':mirai-console')
//include(':mirai-api') //include(':mirai-api')
......
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