Commit 2f67f836 authored by Him188's avatar Him188

Rewrite

parent da18aafa
......@@ -31,17 +31,18 @@ repositories{
您需要将 `VERSION` 替换为最新的版本(如 `0.5.1`): [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/)
Mirai 目前还处于实验性阶段, 建议您时刻保持最新版本.
现在 Mirai 只支持 TIM PC 协议.
**common**
```kotlin
implementation("net.mamoe:mirai-core-common:VERSION")
implementation("net.mamoe:mirai-core-timpc-common:VERSION")
```
**jvm**
```kotlin
implementation("net.mamoe:mirai-core-jvm:VERSION")
implementation("net.mamoe:mirai-core-timpc-jvm:VERSION")
```
**android**
```kotlin
implementation("net.mamoe:mirai-core-android:VERSION")
implementation("net.mamoe:mirai-core-timpc-android:VERSION")
```
## Try
......
......@@ -33,7 +33,7 @@ kotlin {
sourceSets["main"].apply {
dependencies {
implementation(project(":mirai-core"))
implementation(project(":mirai-core-timpc"))
implementation(kotlin("stdlib-jdk8", kotlinVersion))
implementation(kotlin("stdlib-jdk7", kotlinVersion))
......
......@@ -17,9 +17,7 @@ import io.ktor.util.pipeline.ContextDsl
import io.ktor.util.pipeline.PipelineContext
import io.ktor.util.pipeline.PipelineInterceptor
import net.mamoe.mirai.Bot
import net.mamoe.mirai.addFriend
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.getGroup
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.hexToUBytes
......@@ -40,7 +38,7 @@ fun Application.mirai() {
}
mirai("/sendGroupMessage") {
Bot.instanceWhose(qq = param("bot")).getGroup(param<UInt>("group")).sendMessage(param<String>("message"))
Bot.instanceWhose(qq = param("bot")).getGroup(param<Long>("group")).sendMessage(param<String>("message"))
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.asCoroutineDispatcher
......@@ -9,4 +9,5 @@ import java.util.concurrent.Executors
*
* JVM: 独立的 4 thread 调度器
*/
internal actual val NetworkDispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
\ No newline at end of file
internal actual val NetworkDispatcher: CoroutineDispatcher
get() = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
\ No newline at end of file
@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")
package net.mamoe.mirai.contact.internal
package net.mamoe.mirai.timpc.network
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import net.mamoe.mirai.Bot
import kotlinx.coroutines.withContext
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.contact.data.Profile
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.network.protocol.timpc.packet.action.*
import net.mamoe.mirai.network.protocol.timpc.packet.event.MemberJoinEventPacket
import net.mamoe.mirai.network.protocol.timpc.packet.event.MemberQuitEvent
import net.mamoe.mirai.network.qqAccount
import net.mamoe.mirai.network.sessionKey
import net.mamoe.mirai.message.data.ImageId
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.network.data.FriendNameRemark
import net.mamoe.mirai.network.data.GroupInfo
import net.mamoe.mirai.network.data.PreviousNameList
import net.mamoe.mirai.network.data.Profile
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.sendPacket
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.withSession
import net.mamoe.mirai.timpc.TIMPCBot
import net.mamoe.mirai.timpc.internal.RawGroupInfo
import net.mamoe.mirai.timpc.network.packet.action.*
import net.mamoe.mirai.timpc.network.packet.event.MemberJoinEventPacket
import net.mamoe.mirai.timpc.network.packet.event.MemberQuitEvent
import net.mamoe.mirai.timpc.sendPacket
import net.mamoe.mirai.timpc.utils.assertUnreachable
import net.mamoe.mirai.timpc.withTIMPCBot
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.toUHexString
import kotlin.coroutines.CoroutineContext
internal sealed class ContactImpl : Contact {
......@@ -30,22 +35,12 @@ internal sealed class ContactImpl : Contact {
internal abstract suspend fun startUpdater()
}
/**
* 构造 [Group]
*/
@Suppress("FunctionName")
@PublishedApi
internal fun CoroutineScope.Group(bot: Bot, groupId: GroupId, info: RawGroupInfo, context: CoroutineContext): Group =
GroupImpl(bot, groupId, context).apply {
this@apply.info = info.parseBy(this@apply)
launch { startUpdater() }
}
@Suppress("MemberVisibilityCanBePrivate", "CanBeParameter")
internal data class GroupImpl internal constructor(override val bot: Bot, val groupId: GroupId, override val coroutineContext: CoroutineContext) :
internal class GroupImpl internal constructor(bot: TIMPCBot, val groupId: GroupId, override val coroutineContext: CoroutineContext) :
ContactImpl(), Group, CoroutineScope {
override val id: UInt get() = groupId.value
override val bot: TIMPCBot by bot.unsafeWeakRef()
override val id: Long get() = groupId.value
override val internalId = GroupId(id).toInternalId()
internal lateinit var info: GroupInfo
......@@ -56,19 +51,45 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr
override val announcement: String get() = info.announcement
override val members: ContactList<Member> get() = info.members
override fun getMember(id: UInt): Member =
members.getOrNull(id) ?: throw NoSuchElementException("No such member whose id is ${id.toLong()} in group ${groupId.value.toLong()}")
override fun getMember(id: Long): Member =
members.getOrNull(id) ?: throw NoSuchElementException("No such member whose id is $id in group ${groupId.value}")
override suspend fun sendMessage(message: MessageChain) {
bot.sendPacket(GroupPacket.Message(bot.qqAccount, internalId, bot.sessionKey, message))
}
override suspend fun updateGroupInfo(): GroupInfo = bot.withSession {
override suspend fun uploadImage(image: ExternalImage): ImageId = withTIMPCBot {
val userContext = coroutineContext
val response = GroupImagePacket.RequestImageId(bot.qqAccount, internalId, image, sessionKey).sendAndExpect<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 }
}
override suspend fun quit(): QuitGroupResponse = bot.withSession {
GroupPacket.QuitGroup(qqAccount, sessionKey, internalId).sendAndExpect()
override suspend fun quit(): Boolean = withTIMPCBot {
GroupPacket.QuitGroup(qqAccount, sessionKey, internalId).sendAndExpect<GroupPacket.QuitGroupResponse>().isSuccess
}
@UseExperimental(MiraiInternalAPI::class)
......@@ -84,25 +105,45 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr
override fun toString(): String = "Group(${this.id})"
}
@Suppress("FunctionName", "NOTHING_TO_INLINE")
internal inline fun CoroutineScope.QQ(bot: Bot, id: UInt, coroutineContext: CoroutineContext): QQ = QQImpl(bot, id, coroutineContext).apply { launch { startUpdater() } }
@PublishedApi
internal data class QQImpl @PublishedApi internal constructor(override val bot: Bot, override val id: UInt, override val coroutineContext: CoroutineContext) :
internal class QQImpl @PublishedApi internal constructor(bot: TIMPCBot, override val id: Long, override val coroutineContext: CoroutineContext) :
ContactImpl(),
QQ, CoroutineScope {
override val bot: TIMPCBot by bot.unsafeWeakRef()
override suspend fun sendMessage(message: MessageChain) =
bot.sendPacket(SendFriendMessagePacket(bot.qqAccount, id, bot.sessionKey, message))
override suspend fun queryProfile(): Profile = bot.withSession {
override suspend fun uploadImage(image: ExternalImage): ImageId = withTIMPCBot {
FriendImagePacket.RequestImageId(qqAccount, sessionKey, id, image).sendAndExpect<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
}
override suspend fun queryPreviousNameList(): PreviousNameList = bot.withSession {
override suspend fun queryPreviousNameList(): PreviousNameList = withTIMPCBot {
QueryPreviousNamePacket(bot.qqAccount, sessionKey, id).sendAndExpect()
}
override suspend fun queryRemark(): FriendNameRemark = bot.withSession {
override suspend fun queryRemark(): FriendNameRemark = withTIMPCBot {
QueryFriendRemarkPacket(bot.qqAccount, sessionKey, id).sendAndExpect()
}
......@@ -114,10 +155,6 @@ internal data class QQImpl @PublishedApi internal constructor(override val bot:
override fun toString(): String = "QQ(${this.id})"
}
@Suppress("FunctionName", "NOTHING_TO_INLINE")
internal inline fun Group.Member(delegate: QQ, permission: MemberPermission, coroutineContext: CoroutineContext): Member =
MemberImpl(delegate, this, permission, coroutineContext).apply { launch { startUpdater() } }
/**
* 群成员
*/
......@@ -130,13 +167,13 @@ internal data class MemberImpl(
) : QQ by delegate, CoroutineScope, Member, ContactImpl() {
override fun toString(): String = "Member(id=${this.id}, group=${group.id}, permission=$permission)"
override suspend fun mute(durationSeconds: Int): Boolean = bot.withSession {
override suspend fun mute(durationSeconds: Int): Boolean = withTIMPCBot {
require(durationSeconds > 0) { "duration must be greater than 0 second" }
require(durationSeconds <= 30 * 24 * 3600) { "duration must be no more than 30 days" }
if (permission == MemberPermission.OWNER) return false
val operator = group.getMember(bot.qqAccount)
check(operator.id != id) { "The bot is the owner of group ${group.id.toLong()}, it cannot mute itself!" }
check(operator.id != id) { "The bot is the owner of group ${group.id}, it cannot mute itself!" }
when (operator.permission) {
MemberPermission.MEMBER -> return false
MemberPermission.ADMINISTRATOR -> if (permission == MemberPermission.ADMINISTRATOR) return false
......@@ -153,7 +190,7 @@ internal data class MemberImpl(
// TODO: 2019/12/6 更新群成员信息
}
override suspend fun unmute(): Unit = bot.withSession {
override suspend fun unmute(): Unit = withTIMPCBot {
GroupPacket.Mute(qqAccount, group.internalId, sessionKey, id, 0u).sendAndExpect<GroupPacket.MuteResponse>()
}
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.timpc
package net.mamoe.mirai.timpc.network
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.*
import net.mamoe.mirai.event.events.BeforePacketSendEvent
import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.event.Cancellable
import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.BotLoginSucceedEvent
import net.mamoe.mirai.event.events.PacketSentEvent
import net.mamoe.mirai.event.events.ServerPacketReceivedEvent
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.timpc.handler.DataPacketSocketAdapter
import net.mamoe.mirai.network.protocol.timpc.handler.TemporaryPacketHandler
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.network.protocol.timpc.packet.login.*
import net.mamoe.mirai.network.data.LoginResult
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.OnlineStatus
import net.mamoe.mirai.utils.currentBotConfiguration
import net.mamoe.mirai.timpc.TIMPCBot
import net.mamoe.mirai.timpc.network.handler.DataPacketSocketAdapter
import net.mamoe.mirai.timpc.network.handler.TemporaryPacketHandler
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.timpc.network.packet.login.*
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.*
import kotlin.coroutines.CoroutineContext
import kotlin.properties.Delegates
import kotlin.random.Random
/**
......@@ -39,9 +37,9 @@ internal expect val NetworkDispatcher: CoroutineDispatcher
*
* @see BotNetworkHandler
*/
internal class TIMBotNetworkHandler internal constructor(coroutineContext: CoroutineContext, override val bot: Bot) :
BotNetworkHandler<TIMBotNetworkHandler.BotSocketAdapter>, CoroutineScope {
internal class TIMBotNetworkHandler internal constructor(coroutineContext: CoroutineContext, bot: TIMPCBot) :
BotNetworkHandler(), CoroutineScope {
override val bot: TIMPCBot by bot.unsafeWeakRef()
override val supervisor: CompletableJob = SupervisorJob(coroutineContext[Job])
override val coroutineContext: CoroutineContext =
......@@ -50,52 +48,43 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
?: "an unnamed coroutine"} under TIMBotNetworkHandler", e)
} + supervisor
override lateinit var socket: BotSocketAdapter
lateinit var socket: BotSocketAdapter
private set
private val temporaryPacketHandlers = mutableListOf<TemporaryPacketHandler<*, *>>()
private val handlersLock = Mutex()
internal val temporaryPacketHandlers = LockFreeLinkedList<TemporaryPacketHandler<*, *>>()
private var heartbeatJob: Job? = null
suspend fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*, *>) {
handlersLock.withLock {
temporaryPacketHandlers.add(temporaryPacketHandler)
}
temporaryPacketHandler.send(this.session)
}
override suspend fun login() {
override suspend fun login(): LoginResult {
return withContext(this.coroutineContext) {
TIMProtocol.SERVER_IP.sortedBy { Random.nextInt() }.forEach { ip ->
bot.logger.info("Connecting server $ip")
try {
withTimeout(3000) {
socket = BotSocketAdapter(ip)
}
} catch (e: Exception) {
return@withContext LoginResult.NETWORK_UNAVAILABLE
TIMProtocol.SERVER_IP.sortedBy { Random.nextInt() }.forEach { ip ->
bot.logger.info("Connecting server $ip")
try {
withTimeout(3000) {
socket = BotSocketAdapter(ip)
}
} 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()
}
return@withContext LoginResult.TIMEOUT
socket.close()
}
throw LoginFailedException(LoginResult.TIMEOUT)
}
internal var loginResult: CompletableDeferred<LoginResult> = CompletableDeferred()
override lateinit var session: BotSession
internal var loginResult: CompletableDeferred<LoginResult?> = CompletableDeferred()
//private | internal
private var sessionKey: SessionKey by Delegates.notNull()
private var _sessionKey: SessionKey? = null
internal val sessionKey: SessionKey get() = _sessionKey ?: error("sessionKey is not yet initialized")
override suspend fun awaitDisconnection() {
heartbeatJob?.join()
......@@ -187,43 +176,38 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
}
}
internal suspend fun resendTouch(): LoginResult /* = coroutineScope */ {
internal suspend fun resendTouch(): LoginResult? /* = coroutineScope */ {
loginHandler?.close()
loginHandler = LoginHandler()
val expect = expectPacket<TouchPacket.TouchResponse>()
launch { processReceive() }
launch {
if (withTimeoutOrNull(currentBotConfiguration().touchTimeoutMillis) { expect.join() } == null) {
loginResult.complete(LoginResult.TIMEOUT)
expectingTouchResponse = Job(supervisor)
try {
launch { processReceive() }
launch {
if (withTimeoutOrNull(currentBotConfiguration().touchTimeoutMillis) { expectingTouchResponse!!.join() } == null) {
loginResult.complete(LoginResult.TIMEOUT)
}
}
}
sendPacket(TouchPacket(bot.qqAccount, serverIp, false))
sendPacket(TouchPacket(bot.qqAccount, serverIp, false))
return loginResult.await()
}
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 loginResult.await()
} finally {
expectingTouchResponse = null
}
return receiving
}
private var expectingTouchResponse: CompletableJob? = null
private suspend fun <TPacket : Packet> handlePacket0(
sequenceId: UShort,
packet: TPacket,
factory: PacketFactory<TPacket, *>
) {
if (ServerPacketReceivedEvent(bot, packet).broadcast().cancelled)
return
if (packet is TouchPacket.TouchResponse) {
expectingTouchResponse?.complete()
}
if (!packet::class.annotations.filterIsInstance<NoLog>().any()) {
if ((packet as? BroadcastControllable)?.shouldBroadcast != false) {
......@@ -236,12 +220,10 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
is Subscribable -> if ((packet as? BroadcastControllable)?.shouldBroadcast != false) packet.broadcast()
}
// Remove first to release the lock
handlersLock.withLock {
temporaryPacketHandlers.filter { it.filter(session, packet, sequenceId) }
.also { temporaryPacketHandlers.removeAll(it) }
}.forEach {
it.doReceiveCatchingExceptions(packet)
temporaryPacketHandlers.forEach {
if (it.filter(packet, sequenceId) && temporaryPacketHandlers.remove(it)) {
it.doReceivePassingExceptionsToDeferred(packet)
}
}
if (factory is SessionPacketFactory<*>) {
......@@ -256,10 +238,6 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
internal suspend fun sendPacket(packet: OutgoingPacket): Unit = withContext(coroutineContext + CoroutineName("sendPacket")) {
check(channel.isOpen) { "channel is not open" }
if (BeforePacketSendEvent(bot, packet).broadcast().cancelled) {
return@withContext
}
packet.delegate.use { built ->
val buffer = IoBuffer.Pool.borrow()
try {
......@@ -291,8 +269,6 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
bot.logger.verbose("Packet sent: ${it.name}")
}
PacketSentEvent(bot, packet).broadcast()
Unit
}
......@@ -466,8 +442,8 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
}
is RequestSessionPacket.SessionKeyResponse -> {
sessionKey = packet.sessionKey
bot.logger.info("sessionKey = ${sessionKey.value.toUHexString()}")
_sessionKey = packet.sessionKey
bot.logger.info("sessionKey = ${packet.sessionKey.value.toUHexString()}")
setOnlineStatus(OnlineStatus.ONLINE)//required
}
......@@ -475,14 +451,11 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
is ChangeOnlineStatusPacket.ChangeOnlineStatusResponse -> {
BotLoginSucceedEvent(bot).broadcast()
session = BotSession(sessionKey)
val configuration = currentBotConfiguration()
heartbeatJob = this@TIMBotNetworkHandler.launch {
while (socket.isOpen) {
delay(configuration.heartbeatPeriodMillis)
with(session) {
with(bot) {
class HeartbeatTimeoutException : CancellationException("heartbeat timeout")
if (withTimeoutOrNull(configuration.heartbeatTimeoutMillis) {
......@@ -500,7 +473,7 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
}
bot.logger.info("Successfully logged in")
loginResult.complete(LoginResult.SUCCESS)
loginResult.complete(null)
this.close()//The LoginHandler is useless since then
}
}
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.timpc
package net.mamoe.mirai.timpc.network
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.solveIpAddress
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.timpc.handler
package net.mamoe.mirai.timpc.network.handler
import kotlinx.io.core.Closeable
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.ServerPacketReceivedEvent
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.timpc.TIMBotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.packet.OutgoingPacket
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.PlatformDatagramChannel
/**
......
@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.withContext
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.timpc.packet.OutgoingPacket
import net.mamoe.mirai.network.protocol.timpc.packet.Packet
import net.mamoe.mirai.network.sendPacket
import net.mamoe.mirai.network.data.Packet
import kotlin.coroutines.CoroutineContext
import kotlin.reflect.KClass
/**
* 临时数据包处理器
* ```kotlin
* session.addHandler<ClientTouchResponsePacket>{
* toSend { TouchPacket() }
* onExpect {//it: ClientTouchResponsePacket
* //do sth.
* }
* }
* ```
*
* @see BotSession.sendAndExpectAsync
*/
internal class TemporaryPacketHandler<P : Packet, R>(
private val expectationClass: KClass<P>,
private val deferred: CompletableDeferred<R>,
private val fromSession: BotSession,
private val checkSequence: Boolean,
private val checkSequence: UShort? = null,
/**
* 调用者的 [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
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) {
internal suspend inline fun doReceivePassingExceptionsToDeferred(packet: Packet) {
@Suppress("UNCHECKED_CAST")
val ret = try {
withContext(callerContext) {
......@@ -72,4 +33,5 @@ internal class TemporaryPacketHandler<P : Packet, R>(
}
deferred.complete(ret)
}
}
\ No newline at end of file
}
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused")
package net.mamoe.mirai.network.protocol.timpc.packet
package net.mamoe.mirai.timpc.network.packet
/**
* 包的最后一次修改时间, 和分析时使用的 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.IoBuffer
import kotlinx.io.core.writeFully
import net.mamoe.mirai.utils.decryptBy
import net.mamoe.mirai.utils.encryptBy
import net.mamoe.mirai.utils.io.encryptAndWrite
/**
......@@ -15,6 +19,7 @@ internal inline class SessionKey(override val value: ByteArray) : DecrypterByteA
/**
* [ByteArray] 解密器
*/
@PublishedApi
internal interface DecrypterByteArray : Decrypter {
val value: ByteArray
override fun decrypt(input: ByteReadPacket): ByteReadPacket = input.decryptBy(value)
......@@ -50,4 +55,8 @@ internal interface Decrypter {
operator fun plus(another: Decrypter): Decrypter = LinkedDecrypter { another.decrypt(this.decrypt(it)) }
}
internal interface DecrypterType<D : Decrypter>
\ No newline at end of file
internal interface DecrypterType<D : Decrypter>
@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")
package net.mamoe.mirai.network.protocol.timpc.packet
package net.mamoe.mirai.timpc.network.packet
import kotlinx.io.core.*
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.writeQQ
import kotlin.contracts.ExperimentalContracts
......@@ -39,13 +39,11 @@ internal abstract class SessionPacketFactory<TPacket : Packet> : PacketFactory<T
/**
* 在 [BotNetworkHandler] 下处理这个包. 广播事件等.
*/
open suspend fun BotNetworkHandler<*>.handlePacket(packet: TPacket) {}
open suspend fun BotNetworkHandler.handlePacket(packet: TPacket) {}
}
/**
* 构造一个待发送给服务器的数据包.
*
* 若不提供参数 [id], 则会通过注解 [AnnotatedId] 获取 id.
*/
@UseExperimental(ExperimentalContracts::class)
@JvmOverloads
......@@ -76,13 +74,11 @@ internal inline fun PacketFactory<*, *>.buildOutgoingPacket(
/**
* 构造一个待发送给服务器的会话数据包.
*
* 若不提供参数 [id], 则会通过注解 [AnnotatedId] 获取 id.
*/
@UseExperimental(ExperimentalContracts::class)
@JvmOverloads
internal inline fun PacketFactory<*, *>.buildSessionPacket(
bot: UInt,
bot: Long,
sessionKey: SessionKey,
name: String? = null,
id: PacketId = this.id,
......@@ -105,13 +101,11 @@ internal inline fun PacketFactory<*, *>.buildSessionPacket(
/**
* 构造一个待发送给服务器的会话数据包.
*
* 若不提供参数 [id], 则会通过注解 [AnnotatedId] 获取 id.
*/
@UseExperimental(ExperimentalContracts::class)
@JvmOverloads
internal fun <T> PacketFactory<*, *>.buildSessionProtoPacket(
bot: UInt,
bot: Long,
sessionKey: SessionKey,
name: String? = null,
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.readBytes
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.utils.io.toUHexString
/**
* 一个包的数据 (body)
*/
interface Packet
/**
* 被忽略的数据包.
*/
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.timpc.packet
package net.mamoe.mirai.timpc.network.packet
import kotlinx.atomicfu.atomic
import kotlinx.io.core.ByteReadPacket
......@@ -10,6 +10,7 @@ import kotlinx.io.pool.useInstance
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.debugPrint
import net.mamoe.mirai.utils.io.read
......@@ -36,7 +37,7 @@ internal abstract class PacketFactory<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")
fun <T> ByteReadPacket.decodeProtoPacket(
......@@ -69,7 +70,7 @@ internal abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypt
}
internal object UnknownPacketFactory : SessionPacketFactory<UnknownPacket>() {
override suspend fun BotNetworkHandler<*>.handlePacket(packet: UnknownPacket) {
override suspend fun BotNetworkHandler.handlePacket(packet: UnknownPacket) {
ByteArrayPool.useInstance {
packet.body.readAvailable(it)
bot.logger.debug("UnknownPacket(${packet.id.value.toUHexString()}) = " + it.toUHexString())
......@@ -80,7 +81,7 @@ internal object UnknownPacketFactory : SessionPacketFactory<UnknownPacket>() {
override suspend fun ByteReadPacket.decode(
id: PacketId,
sequenceId: UShort,
handler: BotNetworkHandler<*>
handler: BotNetworkHandler
): UnknownPacket {
return UnknownPacket(id, this)
}
......@@ -90,6 +91,6 @@ internal object IgnoredPacketFactory : SessionPacketFactory<IgnoredPacket>() {
override suspend fun ByteReadPacket.decode(
id: PacketId,
sequenceId: UShort,
handler: BotNetworkHandler<*>
handler: BotNetworkHandler
): IgnoredPacket = IgnoredPacket(id)
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.timpc.packet
package net.mamoe.mirai.timpc.network.packet
import net.mamoe.mirai.network.protocol.timpc.packet.action.*
import net.mamoe.mirai.network.protocol.timpc.packet.event.EventPacketFactory
import net.mamoe.mirai.network.protocol.timpc.packet.event.FriendOnlineStatusChangedPacket
import net.mamoe.mirai.network.protocol.timpc.packet.login.*
import net.mamoe.mirai.timpc.network.packet.action.*
import net.mamoe.mirai.timpc.network.packet.event.EventPacketFactory
import net.mamoe.mirai.timpc.network.packet.event.FriendOnlineStatusChangedPacket
import net.mamoe.mirai.timpc.network.packet.login.*
import net.mamoe.mirai.utils.io.toUHexString
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.timpc.packet.action
package net.mamoe.mirai.timpc.network.packet.action
import kotlinx.io.core.*
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.network.protocol.timpc.packet.event.EventPacket
import net.mamoe.mirai.network.data.EventPacket
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.network.data.PreviousNameList
import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.withSession
/**
......@@ -22,9 +23,9 @@ import net.mamoe.mirai.withSession
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
internal object QueryPreviousNamePacket : SessionPacketFactory<PreviousNameList>() {
operator fun invoke(
bot: UInt,
bot: Long,
sessionKey: SessionKey,
target: UInt
target: Long
): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
writeZero(2)
writeQQ(bot)
......@@ -41,7 +42,7 @@ internal object QueryPreviousNamePacket : SessionPacketFactory<PreviousNameList>
// [00 00 00 10] 68 69 6D 31 38 38 E7 9A 84 E5 B0 8F 64 69 63 6B
// [00 00 00 0F] E4 B8 B6 E6 9A 97 E8 A3 94 E5 89 91 E9 AD 94
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): PreviousNameList {
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): PreviousNameList {
// 00 00 00 01 00 00 00 0F E8 87 AA E5 8A A8 E9 A9 BE E9 A9 B6 31 2E 33
val count = readUInt().toInt()
......@@ -54,19 +55,6 @@ internal object QueryPreviousNamePacket : SessionPacketFactory<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 10 40 00 00 10 14 20 00 00 00 00 00 00 00 01 00 00 00 00 00
......@@ -79,20 +67,21 @@ class PreviousNameList(
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
internal object CanAddFriendPacket : SessionPacketFactory<CanAddFriendResponse>() {
operator fun invoke(
bot: UInt,
qq: UInt,
bot: Long,
qq: Long,
sessionKey: SessionKey
): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
writeQQ(qq)
}
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): CanAddFriendResponse =
handler.bot.withSession {
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): CanAddFriendResponse =
with(handler.bot) {
if (remaining > 20) {//todo check
return CanAddFriendResponse.AlreadyAdded(readUInt().qq())
return CanAddFriendResponse.AlreadyAdded(readQQ().qq())
}
val qq: QQ = readUInt().qq()
val qq: QQ = readQQ().qq()
readUByteLVByteArray()
// debugDiscardExact(1)
return when (val state = readUByte().toUInt()) {
......@@ -148,7 +137,7 @@ internal sealed class CanAddFriendResponse : EventPacket {
接受 03 00 00 00 00 01 30 5D 12 93 30 00 14 00 00 00 00 10 30 36 35 39 E4 B8 80 E7 BE 8E E5 A4 A9 E9 9D 99 02 0A 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 1E
*/
inline class FriendAdditionKey(val value: IoBuffer)
internal inline class FriendAdditionKey(val value: IoBuffer)
/**
* 请求一个 32 位 Key, 在添加好友时发出
......@@ -156,8 +145,8 @@ inline class FriendAdditionKey(val value: IoBuffer)
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
internal object RequestFriendAdditionKeyPacket : SessionPacketFactory<RequestFriendAdditionKeyPacket.Response>() {
operator fun invoke(
bot: UInt,
qq: UInt,
bot: Long,
qq: Long,
sessionKey: SessionKey
) = buildSessionPacket(bot, sessionKey) {
//01 00 01 02 B3 74 F6
......@@ -165,7 +154,7 @@ internal object RequestFriendAdditionKeyPacket : SessionPacketFactory<RequestFri
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
discardExact(4)
return Response(FriendAdditionKey(readIoBuffer(readUShort().toInt())))
......@@ -183,8 +172,8 @@ internal object AddFriendPacket : SessionPacketFactory<AddFriendPacket.Response>
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
@Suppress("FunctionName")
fun RequestAdd(
bot: UInt,
qq: UInt,
bot: Long,
qq: Long,
sessionKey: SessionKey,
/**
* 验证消息
......@@ -250,13 +239,13 @@ internal object AddFriendPacket : SessionPacketFactory<AddFriendPacket.Response>
@Suppress("FunctionName")
@PacketVersion(date = "2019.11.20", timVersion = "2.3.2 (21173)")
fun Approve(
bot: UInt,
bot: Long,
sessionKey: SessionKey,
/**
* 好友列表分组的组的 ID. "我的好友" 为 0
*/
friendListId: Short,
qq: UInt,
qq: Long,
/**
* 备注. 不设置则需要为 `null` TODO 需要确认是否还需发送一个设置备注包. 因为测试时若有备注则会多发一个包并且包里面有所设置的备注
*/
......@@ -284,7 +273,7 @@ internal object AddFriendPacket : SessionPacketFactory<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号
return Response
}
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.timpc.packet.action
package net.mamoe.mirai.timpc.network.packet.action
import kotlinx.io.charsets.Charsets
import kotlinx.io.core.*
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.ImageId
import net.mamoe.mirai.message.ImageId0x06
import net.mamoe.mirai.message.requireLength
import net.mamoe.mirai.message.data.ImageId
import net.mamoe.mirai.message.data.ImageId0x06
import net.mamoe.mirai.message.data.requireLength
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.network.protocol.timpc.packet.event.EventPacket
import net.mamoe.mirai.network.qqAccount
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.network.data.EventPacket
import net.mamoe.mirai.network.data.ImageLink
import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.Http
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.withSession
/**
* 上传图片
* 挂起直到上传完成或失败
*
* 在 JVM 下, `SendImageUtilsJvm.kt` 内有多个捷径函数
*
* @throws OverFileSizeMaxException 如果文件过大, 服务器拒绝接收时
*/
suspend fun QQ.uploadImage(image: ExternalImage): ImageId = bot.withSession {
FriendImagePacket.RequestImageId(qqAccount, sessionKey, id, image).sendAndExpect<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
......@@ -58,7 +24,7 @@ internal interface FriendImageResponse : EventPacket
* 图片数据地址.
*/
// TODO: 2019/11/15 应该为 inline class, 但 kotlin 有 bug
data class FriendImageLink(override inline val original: String) : FriendImageResponse, ImageLink {
internal data class FriendImageLink(override inline val original: String) : FriendImageResponse, ImageLink {
override fun toString(): String = "FriendImageLink($original)"
}
......@@ -96,9 +62,9 @@ internal object FriendImageOverFileSizeMax : FriendImageResponse {
internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>() {
@Suppress("FunctionName")
fun RequestImageId(
bot: UInt,
bot: Long,
sessionKey: SessionKey,
target: UInt,
target: Long,
image: ExternalImage
): OutgoingPacket = buildSessionPacket(
bot,
......@@ -119,8 +85,8 @@ internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>()
writeTV(0x08_01u)
writeUVarIntLVPacket(tag = 0x12u, lengthOffset = { it + 1 }) {
writeTUVarint(0x08u, bot)
writeTUVarint(0x10u, target)
writeTUVarint(0x08u, bot.toUInt())
writeTUVarint(0x10u, target.toUInt())
writeTV(0x18_00u)
writeTLV(0x22u, image.md5)
writeTUVarint(0x28u, image.inputSize.toUInt())
......@@ -150,7 +116,7 @@ internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>()
@Suppress("FunctionName")
fun RequestImageLink(
bot: UInt,
bot: Long,
sessionKey: SessionKey,
imageId: ImageId
): OutgoingPacket {
......@@ -207,8 +173,8 @@ internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>()
writeUByte(0x1Au)
writeUByte(0x47u)
writeTUVarint(0x08u, bot)
writeTUVarint(0x10u, bot) // 这里实际上应该是这张图片来自哪个 QQ 号. 但传 bot 也没事.
writeTUVarint(0x08u, bot.toUInt())
writeTUVarint(0x10u, bot.toUInt()) // 这里实际上应该是这张图片来自哪个 QQ 号. 但传 bot 也没事.
writeTLV(0x1Au, imageId.value.toByteArray(Charsets.ISO_8859_1))
writeHex("20 02 30 04 38 20 40 FF 01 50 00 6A 05 32 36 39 33 33 78 01")
}
......@@ -217,7 +183,7 @@ internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>()
override suspend fun ByteReadPacket.decode(
id: PacketId,
sequenceId: UShort,
handler: BotNetworkHandler<*>
handler: BotNetworkHandler
): FriendImageResponse {
// 上传图片, 成功获取ID
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.timpc.packet.action
package net.mamoe.mirai.timpc.network.packet.action
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.packet.Packet
import net.mamoe.mirai.network.protocol.timpc.packet.PacketId
import net.mamoe.mirai.network.protocol.timpc.packet.SessionPacketFactory
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.timpc.network.packet.PacketId
import net.mamoe.mirai.timpc.network.packet.SessionPacketFactory
// 0001
......@@ -26,7 +26,7 @@ import net.mamoe.mirai.network.protocol.timpc.packet.SessionPacketFactory
internal inline class FriendListList(val delegate: List<FriendList>): Packet
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.
}
}
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.timpc.packet.action
package net.mamoe.mirai.timpc.network.packet.action
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.writeFully
import kotlinx.io.core.writeUByte
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.utils.io.writeQQ
/**
......@@ -18,7 +18,7 @@ import net.mamoe.mirai.utils.io.writeQQ
*/
internal object RequestAccountInfoPacket : SessionPacketFactory<RequestAccountInfoPacket.Response>() {
operator fun invoke(
qq: UInt,
qq: Long,
sessionKey: SessionKey
): OutgoingPacket = buildOutgoingPacket {
writeQQ(qq)
......@@ -35,5 +35,5 @@ internal object RequestAccountInfoPacket : SessionPacketFactory<RequestAccountIn
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")
package net.mamoe.mirai.network.protocol.timpc.packet.action
package net.mamoe.mirai.timpc.network.packet.action
import kotlinx.coroutines.withContext
import kotlinx.io.core.ByteReadPacket
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.GroupId
import net.mamoe.mirai.contact.GroupInternalId
import net.mamoe.mirai.contact.withSession
import net.mamoe.mirai.message.ImageId
import net.mamoe.mirai.message.ImageId0x03
import net.mamoe.mirai.message.requireLength
import net.mamoe.mirai.message.data.ImageId0x03
import net.mamoe.mirai.message.data.requireLength
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.network.protocol.timpc.packet.event.EventPacket
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.network.data.EventPacket
import net.mamoe.mirai.network.data.ImageLink
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.timpc.utils.assertUnreachable
import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.Http
import net.mamoe.mirai.utils.assertUnreachable
import net.mamoe.mirai.utils.io.toUHexString
/**
* 上传群图片
* 挂起直到上传完成或失败
*
* 在 JVM 下, `SendImageUtilsJvm.kt` 内有多个捷径函数
*
* @throws OverFileSizeMaxException 如果文件过大, 服务器拒绝接收时
*/
suspend fun Group.uploadImage(image: ExternalImage): ImageId = withSession {
val userContext = coroutineContext
val response = GroupImagePacket.RequestImageId(bot.qqAccount, internalId, image, sessionKey).sendAndExpect<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
// endregion
......@@ -193,7 +152,7 @@ internal object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() {
@Suppress("FunctionName")
fun RequestImageId(
bot: UInt,
bot: Long,
groupInternalId: GroupInternalId,
image: ExternalImage,
sessionKey: SessionKey
......@@ -215,7 +174,7 @@ internal object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() {
private val requestImageLinkHead = ubyteArrayOf(0x08u, 0x01u, 0x12u, 0x03u, 0x98u, 0x01u, 0x2u)
@Suppress("FunctionName")
fun RequestImageLink(
bot: UInt,
bot: Long,
sessionKey: SessionKey,
imageId: ImageId0x03
): OutgoingPacket {
......@@ -242,7 +201,7 @@ internal object GroupImagePacket : SessionPacketFactory<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
data class GroupImageResponseProto(
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.timpc.packet.action
package net.mamoe.mirai.timpc.network.packet.action
import kotlinx.io.core.*
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.contact.internal.Member
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.contact.GroupInternalId
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.contact.groupInternalId
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.internal.toPacket
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.timpc.internal.RawGroupInfo
import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.timpc.utils.unsupportedFlag
import net.mamoe.mirai.timpc.utils.unsupportedType
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.withSession
import kotlin.collections.mutableMapOf
import kotlin.collections.set
/**
* 群资料
*/
@Suppress("MemberVisibilityCanBePrivate") // 将来使用
class GroupInfo(
internal var _group: Group,
internal var _owner: Member,
internal var _name: String,
internal var _announcement: String,
internal var _members: ContactList<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 {
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")
internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketResponse>() {
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)")
fun Message(
bot: UInt,
bot: Long,
groupInternalId: GroupInternalId,
sessionKey: SessionKey,
message: MessageChain
......@@ -109,7 +55,7 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
*/
@PacketVersion(date = "2019.11.28", timVersion = "2.3.2 (21173)")
fun QuitGroup(
bot: UInt,
bot: Long,
sessionKey: SessionKey,
group: GroupInternalId
): OutgoingPacket = buildSessionPacket(bot, sessionKey, name = "GroupPacket.QuitGroup") {
......@@ -122,7 +68,7 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
*/
@PacketVersion(date = "2019.11.27", timVersion = "2.3.2 (21173)")
fun QueryGroupInfo(
bot: UInt,
bot: Long,
groupInternalId: GroupInternalId,
sessionKey: SessionKey
): OutgoingPacket = buildSessionPacket(bot, sessionKey, name = "GroupPacket.QueryGroupInfo", headerSizeHint = 9) {
......@@ -136,10 +82,10 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
*/
@PacketVersion(date = "2019.12.2", timVersion = "2.3.2 (21173)")
fun Mute(
bot: UInt,
bot: Long,
groupInternalId: GroupInternalId,
sessionKey: SessionKey,
target: UInt,
target: Long,
/**
* 0 为取消
*/
......@@ -168,12 +114,22 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
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)")
@UseExperimental(ExperimentalStdlibApi::class)
override suspend fun ByteReadPacket.decode(
id: PacketId,
sequenceId: UShort,
handler: BotNetworkHandler<*>
handler: BotNetworkHandler
): GroupPacketResponse {
return when (val packetType = readUByte().toUInt()) {
0x2Au -> MessageResponse
......@@ -181,7 +137,7 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
0x09u -> {
if (readByte().toInt() == 0) {
QuitGroupResponse(readUInt().groupInternalId())
QuitGroupResponse(readUInt().toLong().groupInternalId())
} else {
QuitGroupResponse(null)
}
......@@ -218,10 +174,10 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
76 E4 B8 DD 00 00
*/
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
val owner = readUInt()
val owner = readUInt().toLong()
discardExact(22)
val groupName = readUByteLVString()
......@@ -234,11 +190,11 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
discardExact(50)
val stop = readUInt() // 标记读取群成员的结束
val stop = readUInt().toLong() // 标记读取群成员的结束
discardExact(1) // 00
val members = mutableMapOf<UInt, MemberPermission>()
val members = mutableMapOf<Long, MemberPermission>()
do {
val qq = readUInt()
val qq = readUInt().toLong()
val status = readUShort().toInt() // 这个群成员的状态, 最后一 bit 为管理员权限. 这里面还包含其他状态
if (qq == owner) {
continue
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.timpc.packet.action
package net.mamoe.mirai.timpc.network.packet.action
import io.ktor.client.HttpClient
import io.ktor.client.request.post
......@@ -17,7 +17,7 @@ import net.mamoe.mirai.contact.GroupId
@Suppress("SpellCheckingInspection")
internal suspend inline fun HttpClient.postImage(
htcmd: String,
uin: UInt,
uin: Long,
groupId: GroupId?,
imageInput: Input,
inputSize: Long,
......@@ -30,7 +30,7 @@ internal suspend inline fun HttpClient.postImage(
path("cgi-bin/httpconn")
parameters["htcmd"] = htcmd
parameters["uin"] = uin.toLong().toString()
parameters["uin"] = uin.toString()
if (groupId != null) parameters["groupcode"] = groupId.value.toLong().toString()
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused", "NO_REFLECTION_IN_CLASS_PATH")
package net.mamoe.mirai.network.protocol.timpc.packet.action
package net.mamoe.mirai.timpc.network.packet.action
import io.ktor.client.request.get
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.utils.Http
/**
* 图片文件过大
*/
class OverFileSizeMaxException : IllegalStateException()
interface ImageLink {
/**
* 原图
*/
val original: String
suspend fun downloadAsByteArray(): ByteArray = download().readBytes()
suspend fun download(): ByteReadPacket = Http.get(original)
}
/*
/**
......@@ -32,8 +12,8 @@ interface ImageLink {
@PacketVersion(date = "2019.10.26", timVersion = "2.3.2 (21173)")
object SubmitImageFilenamePacket : PacketFactory {
operator fun invoke(
bot: UInt,
target: UInt,
bot: Long,
target: Long,
filename: String,
sessionKey: SessionKey
): OutgoingPacket = buildOutgoingPacket {
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.timpc.packet.action
package net.mamoe.mirai.timpc.network.packet.action
import io.ktor.util.date.GMTDate
import kotlinx.io.core.*
import net.mamoe.mirai.contact.data.Gender
import net.mamoe.mirai.contact.data.Profile
import net.mamoe.mirai.network.data.Gender
import net.mamoe.mirai.network.data.Profile
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.utils.io.*
inline class AvatarLink(val value: String) : Packet
......@@ -23,13 +24,13 @@ internal object QueryNicknamePacket : SessionPacketFactory<NicknameMap>() {
*/
@PacketVersion(date = "2019.12.7", timVersion = "2.3.2 (21173)")
operator fun invoke(
bot: UInt,
bot: Long,
sessionKey: SessionKey,
qq: UInt
qq: Long
): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
writeHex("03 00 00 00 00 00 00 00 00 00 00")
writeByte(1)
writeUInt(qq)
writeQQ(qq)
}
/**
......@@ -38,7 +39,7 @@ internal object QueryNicknamePacket : SessionPacketFactory<NicknameMap>() {
*/
@PacketVersion(date = "2019.12.7", timVersion = "2.3.2 (21173)")
operator fun invoke(
bot: UInt,
bot: Long,
sessionKey: SessionKey,
qq: Array<UInt>
): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
......@@ -109,7 +110,7 @@ body=03 00 00 00 00 00 00 00 00 00 00 00 03 8E 3C A6 A0 EE EF 02 07 6C 01 14 E8
04 5D EC AF 48
*/
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): NicknameMap {
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): NicknameMap {
//03 00 00 00 00 00 00 00 00 00 00 12 04 14 37
val type = readUByte().toInt()
if (type == 15) {
......@@ -137,16 +138,16 @@ body=03 00 00 00 00 00 00 00 00 00 00 00 03 8E 3C A6 A0 EE EF 02 07 6C 01 14 E8
internal object RequestProfileAvatarPacket : SessionPacketFactory<AvatarLink>() {
//00 01 00 17 D4 54 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5
operator fun invoke(
bot: UInt,
qq: UInt,
bot: Long,
qq: Long,
sessionKey: SessionKey
): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
writeUShort(0x01u)
writeUInt(qq)
writeQQ(qq)
writeHex("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5")
}
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): AvatarLink {
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): AvatarLink {
println(" RequestProfileAvatarPacket body=${this.readBytes().toUHexString()}")
TODO()
}
......@@ -163,19 +164,19 @@ internal object RequestProfileDetailsPacket : SessionPacketFactory<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
@PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)")
operator fun invoke(
bot: UInt,
qq: UInt,
bot: Long,
qq: Long,
sessionKey: SessionKey
): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
writeUShort(0x01u)
writeUInt(qq)
writeQQ(qq)
writeHex("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5")
}
@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)
val qq = readUInt()
val qq = readUInt().toLong()
discardExact(6)
val map = readTLVMap(tagSize = 2, expectingEOF = true)
//map.printTLVMap("Profile(qq=$qq) raw=")
......@@ -193,7 +194,7 @@ internal object RequestProfileDetailsPacket : SessionPacketFactory<RequestProfil
0x02u -> Gender.FEMALE
0x01u -> Gender.MALE
else -> Gender.SECRET // 猜的
//else -> error("Cannot determine gender, bad value of 0x4E29u: ${map[0x4729u]!![0].toUHexString()}")
//else -> error("Cannot determine gender, bad value of 0x4E29u: ${map[0x4729u]!![0].toHexString()}")
},
birthday = map[0x4E3Fu]?.let { GMTDate(it.toUInt().toLong()) },
personalStatement = map[0x4E33u]?.encodeToString(),
......@@ -209,7 +210,7 @@ internal object RequestProfileDetailsPacket : SessionPacketFactory<RequestProfil
}
internal data class RequestProfileDetailsResponse(
val qq: UInt,
val qq: Long,
val profile: Profile
) : Packet {
//00 01 00 99 6B F8 D2 00 00 00 00 00 29
......
@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.discardExact
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.writeQQ
import net.mamoe.mirai.utils.io.writeZero
/**
* 给好友设置的备注
*/
inline class FriendNameRemark(val value: String) : Packet
internal object QueryFriendRemarkPacket : SessionPacketFactory<FriendNameRemark>() {
/**
* 查询好友的备注
*/
@PacketVersion(date = "2019.11.27", timVersion = "2.3.2 (21173)")
operator fun invoke(
bot: UInt,
bot: Long,
sessionKey: SessionKey,
target: UInt
target: Long
): OutgoingPacket = buildSessionPacket(
bot, sessionKey
) {
......@@ -32,7 +28,7 @@ internal object QueryFriendRemarkPacket : SessionPacketFactory<FriendNameRemark>
writeZero(1)
}
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendNameRemark {
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): FriendNameRemark {
//0D 00 5D DA 3D 0F 59 17 3E 05 00 00 06 E6 9F 90 E4 B9 90 00 00 00 00 00 00
discardExact(11)
return FriendNameRemark(readUShortLVString())
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.timpc.packet.action
package net.mamoe.mirai.timpc.network.packet.action
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.utils.io.writeZero
class FriendList : Packet
@PacketVersion(date = "2019.11.24", timVersion = "2.3.2 (21173)")
internal object RequestFriendListPacket : SessionPacketFactory<FriendList>() {
@PacketVersion(date = "2019.11.24", timVersion = "2.3.2 (21173)")
operator fun invoke(
bot: UInt,
bot: Long,
sessionKey: SessionKey
): OutgoingPacket = buildSessionPacket(
bot, sessionKey, version = TIMProtocol.version0x02
......@@ -22,7 +23,7 @@ internal object RequestFriendListPacket : SessionPacketFactory<FriendList>() {
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()
}
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.timpc.packet.action
package net.mamoe.mirai.timpc.network.packet.action
import kotlinx.io.core.*
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.internal.toPacket
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.md5
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)")
internal object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessagePacket.Response>() {
operator fun invoke(
botQQ: UInt,
targetQQ: UInt,
botQQ: Long,
targetQQ: Long,
sessionKey: SessionKey,
message: MessageChain
): OutgoingPacket = buildSessionPacket(botQQ, sessionKey) {
......@@ -25,14 +26,11 @@ internal object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessage
writeHex("38 03")
writeQQ(botQQ)
writeQQ(targetQQ)
writeFully(md5(buildPacket { writeQQ(targetQQ); writeFully(sessionKey) }.readBytes()))
writeFully(md5(buildPacket { writeQQ(targetQQ); writeFully(sessionKey.value) }.readBytes()))
writeHex("00 0B")
writeRandom(2)
writeTime()
writeHex(
"01 1D" +
" 00 00 00 00"
)
writeHex("01 1D 00 00 00 00")
//消息过多要分包发送
//如果只有一个
......@@ -68,5 +66,5 @@ internal object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessage
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")
package net.mamoe.mirai.network.protocol.timpc.packet.event
package net.mamoe.mirai.timpc.network.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.timpc.packet.Packet
import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.timpc.network.packet.PacketVersion
import net.mamoe.mirai.utils.io.readBoolean
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.timpc.packet.event
package net.mamoe.mirai.timpc.network.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.ConnectionOccupiedEvent
import net.mamoe.mirai.utils.io.encodeToString
/**
* 被挤下线. 只能获取到中文的消息
*/
inline class ConnectionOccupiedEvent(val message: String) : EventPacket {
override fun toString(): String = "ConnectionOccupiedEvent(${message.replace("\n", "")})"
}
internal object ConnectionOccupiedPacketHandler : KnownEventParserAndHandler<ConnectionOccupiedEvent>(0x0030u) {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): ConnectionOccupiedEvent {
discardExact(6)
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.timpc.packet.event
package net.mamoe.mirai.timpc.network.packet.event
import kotlinx.io.core.*
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMBotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.network.sessionKey
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.timpc.network.TIMBotNetworkHandler
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.io.readIoBuffer
......@@ -15,16 +15,16 @@ import net.mamoe.mirai.utils.io.readIoBuffer
* 事件的识别 ID. 在 ACK 时使用
*/
internal class EventPacketIdentity(
val from: UInt,//对于好友消息, 这个是发送人
val to: UInt,//对于好友消息, 这个是bot
val from: Long,//对于好友消息, 这个是发送人
val to: Long,//对于好友消息, 这个是bot
internal val uniqueId: IoBuffer//8
) {
override fun toString(): String = "($from->$to)"
}
internal fun BytePacketBuilder.writeEventPacketIdentity(identity: EventPacketIdentity) = with(identity) {
writeUInt(from)
writeUInt(to)
writeUInt(from.toUInt())
writeUInt(to.toUInt())
writeFully(uniqueId)
}
......@@ -39,20 +39,16 @@ internal fun matchEventPacketFactory(value: UShort): EventParserAndHandler<*> =
@NoLog
@Suppress("FunctionName")
internal object EventPacketFactory : PacketFactory<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(
from = readUInt(),
to = readUInt(),
from = readUInt().toLong(), // clear semantic, don't readQQ() or readGroup()
to = readUInt().toLong(), // clear semantic
uniqueId = readIoBuffer(8)
)
(handler as TIMBotNetworkHandler).socket.sendPacket(EventPacketFactory(id, sequenceId, handler.bot.qqAccount, handler.sessionKey, eventIdentity))
discardExact(2) // 1F 40
return with(matchEventPacketFactory(readUShort())) { parse(handler.bot, eventIdentity) }.also {
if (it is MessagePacket<*, *>) {
it.botVar = handler.bot
}
if (it is EventParserAndHandler<*>) {
@Suppress("UNCHECKED_CAST")
with(it as EventParserAndHandler<in Packet>) {
......@@ -68,7 +64,7 @@ internal object EventPacketFactory : PacketFactory<Packet, SessionKey>(SessionKe
operator fun invoke(
id: PacketId,
sequenceId: UShort,
bot: UInt,
bot: Long,
sessionKey: SessionKey,
identity: EventPacketIdentity
): OutgoingPacket = buildSessionPacket(name = "EventPacket", id = id, sequenceId = sequenceId, bot = bot, sessionKey = sessionKey) {
......@@ -84,7 +80,7 @@ internal interface EventParserAndHandler<TPacket : Packet> {
/**
* 在 [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> {
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.timpc.packet.event
package net.mamoe.mirai.timpc.network.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion
import net.mamoe.mirai.network.protocol.timpc.packet.action.AddFriendPacket
import net.mamoe.mirai.network.qqAccount
import net.mamoe.mirai.event.events.ReceiveFriendAddRequestEvent
import net.mamoe.mirai.timpc.network.packet.PacketVersion
import net.mamoe.mirai.utils.io.readQQ
import net.mamoe.mirai.utils.io.readUShortLVString
import net.mamoe.mirai.withSession
import kotlin.jvm.JvmOverloads
/**
* 陌生人请求添加机器人账号为好友
*/
data class ReceiveFriendAddRequestEvent(
val qq: QQ,
/**
* 验证消息
*/
val message: String
) : EventPacket {
/**
* 同意这个请求
*
* @param remark 备注名, 不设置则需为 `null`
*/
@JvmOverloads
suspend fun approve(remark: String? = null): Unit = qq.bot.withSession {
AddFriendPacket.Approve(qqAccount, sessionKey, 0, qq.id, remark).sendAndExpect<AddFriendPacket.Response>()
}
}
@PacketVersion(date = "2019.11.20", timVersion = "2.3.2 (21173)")
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 01
// 76 E4 B8 DD
......@@ -88,7 +63,7 @@ Mirai 20:35:23 : Packet received: UnknownEventPacket(id=00 BB, identity=(7610254
discardExact(10 + 4) // 00 00 00 08 00 0A 00 04 01 00 00 00 00 01
discardExact(4) // bot account uint
discardExact(4) // 00 00 00 01
val qq = readUInt().qq()
val qq = readQQ().qq()
discardExact(4) // bot account uint
discardExact(3) // 02 00 00 恒定
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.timpc.packet.event
package net.mamoe.mirai.timpc.network.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion
import net.mamoe.mirai.network.data.EventPacket
import net.mamoe.mirai.timpc.network.packet.PacketVersion
import net.mamoe.mirai.utils.io.readQQ
data class FriendConversationInitialize(
val qq: UInt
val qq: Long
) : EventPacket
@PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)")
internal object FriendConversationInitializedEventParserAndHandler : KnownEventParserAndHandler<FriendConversationInitialize>(0x0079u) {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): FriendConversationInitialize {
discardExact(4)// 00 00 00 00
return FriendConversationInitialize(readUInt())
return FriendConversationInitialize(readQQ())
}
}
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "JoinDeclarationAndAssignment")
package net.mamoe.mirai.network.protocol.timpc.packet.event
package net.mamoe.mirai.timpc.network.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUByte
import kotlinx.io.core.readUInt
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.event.events.FriendStatusChanged
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.packet.KnownPacketId
import net.mamoe.mirai.network.protocol.timpc.packet.PacketId
import net.mamoe.mirai.network.protocol.timpc.packet.SessionPacketFactory
import net.mamoe.mirai.timpc.network.packet.KnownPacketId
import net.mamoe.mirai.timpc.network.packet.PacketId
import net.mamoe.mirai.timpc.network.packet.SessionPacketFactory
import net.mamoe.mirai.utils.OnlineStatus
data class FriendStatusChanged(
val qq: QQ,
val status: OnlineStatus
) : EventPacket
import net.mamoe.mirai.utils.io.readQQ
/**
* 好友在线状态改变
*/
internal object FriendOnlineStatusChangedPacket : SessionPacketFactory<FriendStatusChanged>() {
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendStatusChanged {
val qq = readUInt()
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): FriendStatusChanged {
val qq = readQQ()
discardExact(8)
val statusId = readUByte()
val status = OnlineStatus(statusId)
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.timpc.packet.event
package net.mamoe.mirai.timpc.network.packet.event
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion
import net.mamoe.mirai.network.data.EventPacket
import net.mamoe.mirai.timpc.network.packet.PacketVersion
import net.mamoe.mirai.utils.io.debugPrint
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.timpc.packet.event
package net.mamoe.mirai.timpc.network.packet.event
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.data.EventPacket
import net.mamoe.mirai.utils.io.toUHexString
internal inline class IgnoredEventPacket(val id: UShort) : EventPacket {
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "unused", "MemberVisibilityCanBePrivate")
package net.mamoe.mirai.network.protocol.timpc.packet.event
package net.mamoe.mirai.timpc.network.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.contact.internal.Member
import net.mamoe.mirai.contact.internal.MemberImpl
import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.getGroup
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.timpc.TIMPCBot
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.discardExact
import net.mamoe.mirai.utils.io.readQQ
/**
* 成员加入前的事件. 群的成员列表中还没有这个人
......@@ -71,20 +69,23 @@ internal object MemberJoinPacketHandler : KnownEventParserAndHandler<MemberJoinE
discardExact(11) //00 00 00 08 00 0A 00 04 01 00 00
discardExact(1) // 00
val group = bot.getGroup(readUInt())
val group = bot.getGroup(readQQ())
discardExact(1) // 01
val qq = bot.getQQ(readUInt())
val member = group.Member(qq, MemberPermission.MEMBER, qq.coroutineContext)
val qq = bot.getQQ(readQQ())
val member = with(bot) {
this as? TIMPCBot ?: error("wrong Bot type passed")
group.Member(qq, MemberPermission.MEMBER)
}
return if (readByte().toInt() == 0x03) {
MemberJoinEventPacket(member, null)
} 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()
packet.broadcast()
PostMemberJoinEvent(packet).broadcast()
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.timpc.packet.event
package net.mamoe.mirai.timpc.network.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readUByte
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.getGroup
import net.mamoe.mirai.network.data.EventPacket
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.io.discardExact
import net.mamoe.mirai.utils.io.toUHexString
import net.mamoe.mirai.utils.io.unsupported
import net.mamoe.mirai.utils.io.*
/**
* 群成员列表变动事件.
......@@ -53,21 +50,21 @@ internal object MemberGoneEventPacketHandler : KnownEventParserAndHandler<Member
discardExact(11)
discardExact(1)
val group = bot.getGroup(readUInt())
val group = bot.getGroup(readGroup())
discardExact(1)
val id = readUInt()
val id = readQQ()
if (id == bot.qqAccount) {
discardExact(1)
return BeingKickEvent(group, group.getMember(readUInt()))
return BeingKickEvent(group, group.getMember(readQQ()))
}
val member = group.getMember(id)
return when (val type = readUByte().toInt()) {
0x02 -> MemberQuitEvent(member, _operator = null)
0x03 -> MemberQuitEvent(member, _operator = group.getMember(readUInt()))
else -> unsupported("Unsupported type " + type.toUHexString())
0x03 -> MemberQuitEvent(member, _operator = group.getMember(readQQ()))
else -> error("Unsupported type " + type.toUHexString())
}
// 某群员主动离开, 群号 853343432
......@@ -94,5 +91,4 @@ internal object MemberGoneEventPacketHandler : KnownEventParserAndHandler<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
}
}
}
\ No newline at end of file
@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.discardExact
......@@ -9,75 +9,13 @@ import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.getGroup
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.qqAccount
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.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(
val remaining: ByteArray
) : EventOfMute() {
......@@ -86,11 +24,6 @@ internal class Unknown0x02DCPacketFlag0x0EMaybeMutePacket(
override fun toString(): String = "Unknown0x02DCPacketFlag0x0EMaybeMutePacket(remaining=${remaining.toUHexString()})"
}
sealed class EventOfMute : EventPacket {
abstract val operator: Member
abstract val group: Group
}
// TODO: 2019/12/14 这可能不只是禁言的包.
internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandler<EventOfMute>(0x02DCu) {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): EventOfMute {
......@@ -133,12 +66,12 @@ internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandl
0x11u -> debugPrintIfFail("解析禁言包(0x02DC)时"){ // 猜测这个失败是撤回??
discardExact(15)
discardExact(2)
val group = bot.getGroup(readUInt())
val group = bot.getGroup(readQQ())
discardExact(2)
val operator = group.getMember(readUInt())
val operator = group.getMember(readQQ())
discardExact(4) //time
discardExact(2)
val memberQQ = readUInt()
val memberQQ = readQQ()
val durationSeconds = readUInt().toInt()
if (durationSeconds == 0) {
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.timpc.packet.event
package net.mamoe.mirai.timpc.network.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.timpc.packet.Packet
import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.timpc.network.packet.PacketVersion
import net.mamoe.mirai.utils.io.readQQ
data class MemberPermissionChangePacket(
val groupId: UInt,
val qq: UInt,
val groupId: Long,
val qq: Long,
val kind: Kind
) : Packet {
enum class Kind {
......@@ -35,7 +35,7 @@ internal object GroupMemberPermissionChangedEventFactory : KnownEventParserAndHa
// 取消管理员
// 00 00 00 08 00 0A 00 04 01 00 00 00 22 96 29 7B 01 00 76 E4 B8 DD 00
discardExact(remaining - 5)
val qq = readUInt()
val qq = readQQ()
val kind = when (readByte().toInt()) {
0x00 -> MemberPermissionChangePacket.Kind.NO_LONGER_OPERATOR
0x01 -> MemberPermissionChangePacket.Kind.BECOME_OPERATOR
......
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")
package net.mamoe.mirai.network.protocol.timpc.packet.event
package net.mamoe.mirai.timpc.network.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.data.EventPacket
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.toUHexString
......@@ -28,11 +29,11 @@ Mirai 21:54:15 : Packet received: UnknownEventPacket(id=00 57, identity=(9205034
internal class UnknownEventParserAndHandler(override val id: UShort) : EventParserAndHandler<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.
}
override suspend fun BotNetworkHandler<*>.handlePacket(packet: UnknownEventPacket) {
override suspend fun BotNetworkHandler.handlePacket(packet: UnknownEventPacket) {
ByteArrayPool.useInstance {
packet.body.readAvailable(it)
bot.logger.debug("Unknown packet(${packet.id}) data = " + it.toUHexString())
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused", "FunctionName")
package net.mamoe.mirai.network.protocol.timpc.packet.login
package net.mamoe.mirai.timpc.network.packet.login
import kotlinx.io.core.*
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.utils.io.*
internal object CaptchaKey : DecrypterByteArray, DecrypterType<CaptchaKey> {
......@@ -17,7 +18,7 @@ internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, Cap
* 请求验证码传输
*/
fun RequestTransmission(
bot: UInt,
bot: Long,
token0825: ByteArray,
captchaSequence: Int,
token00BA: ByteArray
......@@ -45,7 +46,7 @@ internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, Cap
* 刷新验证码
*/
fun Refresh(
bot: UInt,
bot: Long,
token0825: ByteArray
): OutgoingPacket = buildOutgoingPacket(name = "CaptchaPacket.Refresh") {
writeQQ(bot)
......@@ -67,7 +68,7 @@ internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, Cap
* 提交验证码
*/
fun Submit(
bot: UInt,
bot: Long,
token0825: ByteArray,
captcha: String,
captchaToken: IoBuffer
......@@ -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()) {
0x14u -> {//00 05 00 00 00 00 00 00 38
CaptchaResponse.Correct().apply {
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.timpc.packet.login
package net.mamoe.mirai.timpc.network.packet.login
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.writeFully
import kotlinx.io.core.writeUByte
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.utils.OnlineStatus
import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.utils.io.writeHex
import net.mamoe.mirai.utils.io.writeQQ
......@@ -18,7 +18,7 @@ import net.mamoe.mirai.utils.io.writeQQ
*/
internal object ChangeOnlineStatusPacket : PacketFactory<ChangeOnlineStatusPacket.ChangeOnlineStatusResponse, NoDecrypter>(NoDecrypter) {
operator fun invoke(
bot: UInt,
bot: Long,
sessionKey: SessionKey,
loginStatus: OnlineStatus
): OutgoingPacket = buildOutgoingPacket {
......@@ -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 suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): ChangeOnlineStatusResponse =
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): ChangeOnlineStatusResponse =
ChangeOnlineStatusResponse
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.timpc.packet.login
package net.mamoe.mirai.timpc.network.packet.login
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.writeFully
import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.utils.io.writeHex
import net.mamoe.mirai.utils.io.writeQQ
@NoLog
internal object HeartbeatPacket : SessionPacketFactory<HeartbeatPacketResponse>() {
operator fun invoke(
bot: UInt,
bot: Long,
sessionKey: SessionKey
): OutgoingPacket = buildOutgoingPacket {
writeQQ(bot)
......@@ -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
}
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.timpc.packet.login
package net.mamoe.mirai.timpc.network.packet.login
import kotlinx.io.core.*
import net.mamoe.mirai.contact.data.Gender
import net.mamoe.mirai.network.data.Gender
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.decryptBy
import net.mamoe.mirai.utils.encryptBy
import net.mamoe.mirai.network.data.LoginResult
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.writeCRC32
internal object ShareKey : DecrypterByteArray, DecrypterType<ShareKey> {
override val value: ByteArray = TIMProtocol.shareKey
......@@ -46,7 +45,7 @@ internal inline class SubmitPasswordResponseDecrypter(private val privateKey: Pr
*/
internal object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse, SubmitPasswordResponseDecrypter>(SubmitPasswordResponseDecrypter) {
operator fun invoke(
bot: UInt,
bot: Long,
password: String,
loginTime: Int,
loginIP: String,
......@@ -109,7 +108,7 @@ internal object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginR
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()
return when {
size == 229 || size == 271 || size == 207 || size == 165 /* TODO CHECK 165 */ -> {
......@@ -267,7 +266,7 @@ internal inline class SessionResponseDecryptionKey(private val delegate: IoBuffe
}
private fun BytePacketBuilder.writePart1(
qq: UInt,
qq: Long,
password: String,
loginTime: Int,
loginIP: String,
......@@ -296,7 +295,7 @@ private fun BytePacketBuilder.writePart1(
this.writeFully(TIMProtocol.passwordSubmissionTLV2)
this.writeHex("00 1A")//tag
this.writeHex("00 40")//length
this.writeFully(TIMProtocol.passwordSubmissionTLV2.encryptBy(privateKey))
this.writeFully(TIMProtocol.passwordSubmissionTLV2.encryptBy(privateKey.value))
this.writeFully(TIMProtocol.constantData1)
this.writeFully(TIMProtocol.constantData2)
this.writeQQ(qq)
......@@ -310,6 +309,29 @@ private fun BytePacketBuilder.writePart1(
this.writeHex("60 C9 5D A7 45 70 04 7F 21 7D 84 50 5C 66 A5 C6")//key
}
private fun BytePacketBuilder.writeTLV0006(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: PrivateKey) {
val firstMD5 = md5(password)
val secondMD5 = md5(firstMD5 + byteArrayOf(0, 0, 0, 0) + qq.toUInt().toByteArray())
this.encryptAndWrite(secondMD5) {
writeRandom(4)
writeHex("00 02")
writeQQ(qq)
writeFully(TIMProtocol.constantData2)
writeHex("00 00 01")
writeFully(firstMD5)
writeInt(loginTime)
writeByte(0)
writeZero(4 * 3)
writeIP(loginIP)
writeZero(8)
writeHex("00 10")//这两个hex是passwordSubmissionTLV2的末尾
writeHex("15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B")//16
writeFully(privateKey.value)
}
}
private fun BytePacketBuilder.writePart2() {
this.writeHex("03 12")//tag
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "FunctionName")
package net.mamoe.mirai.network.protocol.timpc.packet.login
package net.mamoe.mirai.timpc.network.packet.login
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.writeFully
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.network.qqAccount
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.withSession
internal fun BotSession.RequestSKeyPacket(): OutgoingPacket = RequestSKeyPacket(qqAccount, sessionKey)
internal inline class SKey(
val value: String
......@@ -25,7 +21,7 @@ internal inline class SKey(
*/
internal object RequestSKeyPacket : SessionPacketFactory<SKey>() {
operator fun invoke(
bot: UInt,
bot: Long,
sessionKey: SessionKey
): OutgoingPacket = buildOutgoingPacket {
writeQQ(bot)
......@@ -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
discardExact(4)
......@@ -44,9 +40,9 @@ internal object RequestSKeyPacket : SessionPacketFactory<SKey>() {
}
}
override suspend fun BotNetworkHandler<*>.handlePacket(packet: SKey) = bot.withSession {
_sKey = packet.value
_cookies = "uin=o$qqAccount;skey=$sKey;"
override suspend fun BotNetworkHandler.handlePacket(packet: SKey) {
// _sKey = packet.value
// _cookies = "uin=o$qqAccount;skey=$sKey;"
// TODO: 2019/11/27 SKEY 实现
/*
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.timpc.packet.login
package net.mamoe.mirai.timpc.network.packet.login
import kotlinx.io.core.*
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.localIpAddress
internal object RequestSessionPacket : PacketFactory<RequestSessionPacket.SessionKeyResponse, SessionResponseDecryptionKey>(SessionResponseDecryptionKey) {
operator fun invoke(
bot: UInt,
bot: Long,
serverIp: String,
token38: IoBuffer,
token88: IoBuffer,
......@@ -65,7 +66,7 @@ internal object RequestSessionPacket : PacketFactory<RequestSessionPacket.Sessio
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) {
407L -> {
discardExact(25)//todo test
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.timpc.packet.login
package net.mamoe.mirai.timpc.network.packet.login
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import kotlinx.io.core.writeFully
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.utils.io.*
internal object TouchKey : DecrypterByteArray, DecrypterType<TouchKey> {
......@@ -22,7 +23,7 @@ internal object TouchKey : DecrypterByteArray, DecrypterType<TouchKey> {
*/
internal object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey>(TouchKey) {
operator fun invoke(
bot: UInt,
bot: Long,
serverIp: String,
isRedirect: Boolean
): OutgoingPacket = buildOutgoingPacket {
......@@ -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()) {
0xFE -> {
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.withContext
import kotlinx.io.core.copyTo
import kotlinx.io.InputStream
import kotlinx.io.core.use
import kotlinx.io.streams.asInput
import kotlinx.io.streams.asOutput
import kotlinx.io.streams.inputStream
import net.mamoe.mirai.Bot
import net.mamoe.mirai.message.Image
import net.mamoe.mirai.network.protocol.timpc.packet.SessionKey
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.message.data.Image
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 java.awt.image.BufferedImage
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import javax.imageio.ImageIO
import kotlin.coroutines.CoroutineContext
/**
* JVM 平台相关扩展. 详情查看 [BotSessionBase]
*/
@UseExperimental(MiraiInternalAPI::class)
@Suppress("unused")
actual class BotSession internal actual constructor(
bot: Bot,
sessionKey: SessionKey
) : BotSessionBase(bot, sessionKey) {
internal actual class TIMPCBot actual constructor(
account: BotAccount,
logger: MiraiLogger?,
context: CoroutineContext
) : TIMPCBotBase(account, logger, context) {
suspend inline fun Image.downloadAsStream(): InputStream = download().inputStream()
suspend inline fun Image.downloadAsBufferedImage(): BufferedImage = withContext(IO) { downloadAsStream().use { ImageIO.read(it) } }
suspend inline fun Image.downloadAsExternalImage(): ExternalImage = download().use { it.toExternalImage() }
suspend inline fun Image.downloadTo(file: File) = file.outputStream().use { downloadTo(it) }
/**
* 需要调用者自行 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.asCoroutineDispatcher
import java.util.concurrent.Executors
/**
* 独立的 4 thread 调度器
* 包处理协程调度器.
*
* JVM: 独立的 4 thread 调度器
*/
internal actual val NetworkDispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
\ No newline at end of file
internal actual val NetworkDispatcher: CoroutineDispatcher
get() = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package mirai.test
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import net.mamoe.mirai.Bot
import net.mamoe.mirai.login
import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult
import net.mamoe.mirai.network.data.LoginResult
import net.mamoe.mirai.timpc.TIMPC
import java.util.*
/**
......@@ -44,8 +42,8 @@ suspend fun main() {
.map { Pair(it[0].toLong(), it[1]) }
.forEach { (qq, password) ->
runBlocking {
val bot = Bot(
qq.toUInt(),
val bot = TIMPC.Bot(
qq,
if (password.endsWith(".")) password.substring(0, password.length - 1) else password
)
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "MemberVisibilityCanBePrivate", "EXPERIMENTAL_UNSIGNED_LITERALS")
package mirai.test.packetdebugger
import PacketDebugger.dataReceived
import PacketDebugger.dataSent
import PacketDebugger.qq
import PacketDebugger.sessionKey
import io.ktor.util.date.GMTDate
import kotlinx.coroutines.*
import kotlinx.io.core.*
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import kotlinx.io.core.readUShort
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.internal.ArrayListSerializer
import kotlinx.serialization.json.Json
import mirai.test.packetdebugger.PacketDebugger.dataReceived
import mirai.test.packetdebugger.PacketDebugger.dataSent
import mirai.test.packetdebugger.PacketDebugger.qq
import mirai.test.packetdebugger.PacketDebugger.sessionKey
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
import net.mamoe.mirai.network.protocol.timpc.handler.DataPacketSocketAdapter
import net.mamoe.mirai.network.protocol.timpc.packet.*
import net.mamoe.mirai.network.protocol.timpc.packet.event.IgnoredEventPacket
import net.mamoe.mirai.network.protocol.timpc.packet.login.CaptchaKey
import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult
import net.mamoe.mirai.network.protocol.timpc.packet.login.ShareKey
import net.mamoe.mirai.network.protocol.timpc.packet.login.TouchKey
import net.mamoe.mirai.timpc.TIMPC
import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.timpc.network.packet.event.IgnoredEventPacket
import net.mamoe.mirai.timpc.network.packet.login.CaptchaKey
import net.mamoe.mirai.timpc.network.packet.login.ShareKey
import net.mamoe.mirai.timpc.network.packet.login.TouchKey
import net.mamoe.mirai.utils.DecryptionFailedException
import net.mamoe.mirai.utils.decryptBy
import net.mamoe.mirai.utils.io.*
......@@ -37,7 +36,6 @@ import java.nio.charset.Charset
import java.util.concurrent.Executors
import kotlin.concurrent.thread
import kotlin.coroutines.CoroutineContext
import kotlin.io.use
/**
* 避免 print 重叠. 单线程处理足够调试
......@@ -189,7 +187,7 @@ internal object PacketDebugger {
/**
* null 则不筛选
*/
val qq: UInt? = 761025446u
val qq: Long? = 761025446
/**
* 打开后则记录每一个包到文件.
*/
......@@ -208,7 +206,7 @@ internal object PacketDebugger {
discardExact(3)
val id = matchPacketId(readUShort())
val sequenceId = readUShort()
val packetQQ = readUInt()
val packetQQ = readQQ()
if (id == KnownPacketId.HEARTBEAT || (qq != null && packetQQ != qq))
return@read
......@@ -308,7 +306,7 @@ internal object PacketDebugger {
if (IgnoredPacketIdList.contains(id)) {
return
}
val packetQQ = readUInt()
val packetQQ = readQQ()
if (qq != null && packetQQ != qq) {
return@read
}
......@@ -378,27 +376,12 @@ when (idHex.substring(0, 5)) {
}
internal object DebugNetworkHandler : BotNetworkHandler<DataPacketSocketAdapter>, CoroutineScope {
internal object DebugNetworkHandler : BotNetworkHandler(), CoroutineScope {
override val supervisor: CompletableJob = SupervisorJob()
override val socket: DataPacketSocketAdapter = object : DataPacketSocketAdapter {
override val serverIp: String
get() = ""
override val channel: PlatformDatagramChannel
get() = error("UNSUPPORTED")
override val isOpen: Boolean
get() = true
override fun close() {
}
override val owner: Bot
get() = bot
}
override val bot: Bot = Bot(qq ?: 0u, "", coroutineContext)
override val session = BotSession(bot, SessionKey(byteArrayOf()))
override val bot: Bot = TIMPC.run { this@DebugNetworkHandler.Bot(qq ?: 0L, "", null) }
override suspend fun login(): LoginResult = LoginResult.SUCCESS
override suspend fun login() {}
override suspend fun awaitDisconnection() {
}
......
package mirai.test.packetdebugger
package packetdebugger
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUShort
import net.mamoe.mirai.network.protocol.timpc.packet.PacketId
import net.mamoe.mirai.network.protocol.timpc.packet.login.CaptchaPacket
import net.mamoe.mirai.network.protocol.timpc.packet.matchPacketId
import net.mamoe.mirai.timpc.network.packet.PacketId
import net.mamoe.mirai.timpc.network.packet.matchPacketId
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
......
<?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.QQ
import net.mamoe.mirai.utils.MiraiInternalAPI
......@@ -8,7 +9,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
* 平台相关扩展
*/
@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: URL): 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
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.util.KtorExperimentalAPI
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.readBytes
import java.io.ByteArrayOutputStream
import java.io.DataInput
import java.io.EOFException
......@@ -24,7 +22,7 @@ actual val deviceName: String get() = InetAddress.getLocalHost().hostName
* Ktor HttpClient. 不同平台使用不同引擎.
*/
@KtorExperimentalAPI
internal actual val Http: HttpClient
actual val Http: HttpClient
get() = HttpClient(CIO)
/**
......
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
import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Job
import kotlinx.io.OutputStream
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.use
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult
import net.mamoe.mirai.network.data.AddFriendResult
import net.mamoe.mirai.network.data.ImageLink
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.GroupNotFoundException
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.io.transferTo
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
import kotlin.jvm.JvmSynthetic
/**
* Mirai 的机器人. 一个机器人实例登录一个 QQ 账号.
......@@ -22,77 +25,60 @@ import kotlin.jvm.JvmSynthetic
*
* @see Contact
*/
interface Bot : CoroutineScope {
abstract class Bot : CoroutineScope {
@UseExperimental(MiraiInternalAPI::class)
companion object {
suspend inline operator fun invoke(account: BotAccount, logger: MiraiLogger): Bot = BotImpl(account, logger, coroutineContext)
suspend inline operator fun invoke(account: BotAccount): Bot = BotImpl(account, context = coroutineContext)
@JvmSynthetic
suspend inline operator fun invoke(qq: UInt, password: String): Bot = BotImpl(BotAccount(qq, password), context = coroutineContext)
suspend inline operator fun invoke(qq: Long, password: String): Bot = BotImpl(BotAccount(qq.toUInt(), password), context = coroutineContext)
operator fun invoke(qq: Long, password: String, context: CoroutineContext): Bot = BotImpl(BotAccount(qq, password), context = context)
@JvmSynthetic
operator fun invoke(qq: UInt, password: String, context: CoroutineContext): Bot = BotImpl(BotAccount(qq, password), context = context)
operator fun invoke(account: BotAccount, context: CoroutineContext): Bot = BotImpl(account, context = context)
inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block)
fun instanceWhose(qq: UInt): Bot = BotImpl.instanceWhose(qq = qq)
fun instanceWhose(qq: Long): Bot = BotImpl.instanceWhose(qq = qq)
}
/**
* 账号信息
*/
val account: BotAccount
abstract val account: BotAccount
/**
* 日志记录器
*/
val logger: MiraiLogger
abstract val logger: MiraiLogger
override val coroutineContext: CoroutineContext
abstract override val coroutineContext: CoroutineContext
// region contacts
/**
* 与这个机器人相关的 QQ 列表. 机器人与 QQ 不一定是好友
*/
val qqs: ContactList<QQ>
/**
* 获取缓存的 QQ 对象. 若没有对应的缓存, 则会线程安全地创建一个.
*/
fun getQQ(id: UInt): QQ
abstract val qqs: ContactList<QQ>
/**
* 获取缓存的 QQ 对象. 若没有对应的缓存, 则会线程安全地创建一个.
*/
fun getQQ(id: Long): QQ
abstract fun getQQ(id: Long): QQ
/**
* 与这个机器人相关的群列表. 机器人不一定是群成员.
*/
val groups: ContactList<Group>
abstract val groups: ContactList<Group>
/**
* 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个.
* 若 [id] 无效, 将会抛出 [GroupNotFoundException]
*/
suspend fun getGroup(id: GroupId): Group
abstract suspend fun getGroup(id: GroupId): Group
/**
* 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个.
* 若 [internalId] 无效, 将会抛出 [GroupNotFoundException]
*/
suspend fun getGroup(internalId: GroupInternalId): Group
abstract suspend fun getGroup(internalId: GroupInternalId): Group
/**
* 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个.
* 若 [id] 无效, 将会抛出 [GroupNotFoundException]
*/
suspend fun getGroup(id: Long): Group
abstract suspend fun getGroup(id: Long): Group
// endregion
......@@ -101,36 +87,64 @@ interface Bot : CoroutineScope {
/**
* 网络模块
*/
val network: BotNetworkHandler<*>
abstract val network: BotNetworkHandler
/**
* [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [BotNetworkHandler] 下的协程.
* 然后重新启动并尝试登录
* 使用在默认配置基础上修改的配置进行登录
*/
fun tryReinitializeNetworkHandler(
configuration: BotConfiguration,
cause: Throwable? = null
): Job
suspend inline fun login(configuration: BotConfiguration.() -> Unit) {
return this.login(BotConfiguration().apply(configuration))
}
/**
* 使用特定配置进行登录
*/
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(
configuration: BotConfiguration,
cause: Throwable? = null
): LoginResult
abstract suspend fun addFriend(id: Long, message: String? = null, remark: String? = null): AddFriendResult
/**
* [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [BotNetworkHandler] 下的协程.
* 然后重新启动并尝试登录
* 同意来自陌生人的加好友请求
*/
fun reinitializeNetworkHandlerAsync(
configuration: BotConfiguration,
cause: Throwable? = null
): Deferred<LoginResult>
abstract suspend fun approveFriendAddRequest(id: Long, remark: String?)
// endregion
fun close()
abstract fun close(throwable: Throwable?)
// region extensions
fun Int.qq(): QQ = getQQ(this.toLong())
fun Long.qq(): QQ = getQQ(this)
suspend inline fun Int.group(): Group = getGroup(this.toLong())
suspend inline fun Long.group(): Group = getGroup(this)
suspend inline fun GroupInternalId.group(): Group = getGroup(this)
suspend inline fun GroupId.group(): Group = getGroup(this)
/**
* 需要调用者自行 close [output]
*/
@UseExperimental(KtorExperimentalAPI::class)
suspend inline fun Image.downloadTo(output: OutputStream) =
download().use { input -> input.transferTo(output) }
// endregion
}
\ No newline at end of file
......@@ -3,8 +3,6 @@
package net.mamoe.mirai
data class BotAccount(
val id: UInt,
val id: Long,
val password: String
) {
constructor(id: Long, password: String) : this(id.toUInt(), password)
}
\ No newline at end of file
)
\ No newline at end of file
@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:JvmName("BotHelperKt")
@file:Suppress("unused", "EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE")
@file:Suppress("unused", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.timpc.TIMBotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.packet.OutgoingPacket
import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult
import net.mamoe.mirai.network.protocol.timpc.packet.login.requireSuccess
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.internal.PositiveNumbers
import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
import kotlin.jvm.JvmOverloads
/*
* 在 [Bot] 中的方法的捷径
*/
//Contacts
suspend inline fun Bot.getGroup(id: UInt): Group = this.getGroup(GroupId(id))
/**
* 以 [BotSession] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值.
* 这个方法将能帮助使用在 [BotSession] 中定义的一些扩展方法, 如 [BotSession.sendAndExpectAsync]
*/
@UseExperimental(ExperimentalContracts::class)
inline fun <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]
*/
suspend inline fun Bot.alsoLogin(): Bot = apply { login().requireSuccess() }
suspend inline fun Bot.alsoLogin(configuration: BotConfiguration = BotConfiguration.Default): Bot =
apply { login(configuration) }
/**
* 使用在默认配置基础上修改的配置进行登录, 返回 [this]
......@@ -77,20 +30,11 @@ suspend inline fun Bot.alsoLogin(configuration: BotConfiguration.() -> Unit): Bo
contract {
callsInPlace(configuration, InvocationKind.EXACTLY_ONCE)
}
this.reinitializeNetworkHandler(BotConfiguration().apply(configuration)).requireSuccess()
this.login(configuration)
return this
}
/**
* 使用默认的配置 ([BotConfiguration.Default]) 登录, 返回 [this]
*/
suspend inline fun Bot.alsoLogin(message: String): Bot {
return this.apply {
login().requireSuccess { message } // requireSuccess is inline, so no performance waste
}
}
/**
* 取得机器人的 QQ 号
*/
inline val Bot.qqAccount: UInt get() = this.account.id
\ No newline at end of file
inline val Bot.qqAccount: Long get() = this.account.id
\ No newline at end of file
......@@ -2,54 +2,43 @@
package net.mamoe.mirai
import kotlinx.coroutines.*
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.contact.internal.Group
import net.mamoe.mirai.contact.internal.QQ
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMBotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.packet.KnownPacketId
import net.mamoe.mirai.network.protocol.timpc.packet.action.GroupNotFound
import net.mamoe.mirai.network.protocol.timpc.packet.action.GroupPacket
import net.mamoe.mirai.network.protocol.timpc.packet.action.RawGroupInfo
import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult
import net.mamoe.mirai.network.protocol.timpc.packet.login.isSuccess
import net.mamoe.mirai.network.qqAccount
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.internal.PositiveNumbers
import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
import net.mamoe.mirai.utils.io.inline
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.io.logStacktrace
import kotlin.coroutines.CoroutineContext
import kotlin.jvm.JvmSynthetic
@PublishedApi
internal class BotImpl @PublishedApi internal constructor(
/*
* 泛型 N 不需要向外(接口)暴露.
*/
@MiraiInternalAPI
abstract class BotImpl<N : BotNetworkHandler> constructor(
override val account: BotAccount,
override val logger: MiraiLogger = DefaultLogger("Bot(" + account.id + ")"),
context: CoroutineContext
) : Bot, CoroutineScope {
) : Bot(), CoroutineScope {
private val supervisorJob = SupervisorJob(context[Job])
override val coroutineContext: CoroutineContext =
context + supervisorJob + CoroutineExceptionHandler { _, e -> e.logStacktrace("An exception was thrown under a coroutine of Bot") }
init {
launch {
instances.addLast(this@BotImpl)
}
@Suppress("LeakingThis")
instances.addLast(this)
}
companion object {
init {
KnownPacketId.values() /* load id classes */
}
@PublishedApi
internal val instances: LockFreeLinkedList<Bot> = LockFreeLinkedList()
inline fun forEachInstance(block: (Bot) -> Unit) = instances.forEach(block)
fun instanceWhose(qq: UInt): Bot {
fun instanceWhose(qq: Long): Bot {
instances.forEach {
if (it.qqAccount == qq) {
return it
......@@ -63,101 +52,21 @@ internal class BotImpl @PublishedApi internal constructor(
// region network
override val network: BotNetworkHandler<*> get() = _network
private lateinit var _network: BotNetworkHandler<*>
override fun tryReinitializeNetworkHandler(// shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close
configuration: BotConfiguration,
cause: Throwable?
): Job = launch {
repeat(configuration.reconnectionRetryTimes) {
if (reinitializeNetworkHandlerAsync(configuration, cause).await().isSuccess()) {
logger.info("Reconnected successfully")
return@launch
} else {
delay(configuration.reconnectPeriodMillis)
}
}
}
override suspend fun reinitializeNetworkHandler(
configuration: BotConfiguration,
cause: Throwable?
): LoginResult {
logger.info("BotAccount: ${qqAccount.toLong()}")
logger.info("Initializing BotNetworkHandler")
try {
if (::_network.isInitialized) {
_network.close(cause)
}
} catch (e: Exception) {
logger.error("Cannot close network handler", e)
}
_network = TIMBotNetworkHandler(this.coroutineContext + configuration, this)
return _network.login()
}
override fun reinitializeNetworkHandlerAsync(
configuration: BotConfiguration,
cause: Throwable?
): Deferred<LoginResult> = async { reinitializeNetworkHandler(configuration, cause) }
abstract override val network: N
// endregion
// region contacts
override val groups: ContactList<Group> = ContactList(LockFreeLinkedList())
override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList())
/**
* 线程安全地获取缓存的 QQ 对象. 若没有对应的缓存, 则会创建一个.
*/
@UseExperimental(MiraiInternalAPI::class)
@JvmSynthetic
override fun getQQ(id: UInt): QQ = qqs.delegate.getOrAdd(id) { QQ(this, id, coroutineContext) }
@UseExperimental(MiraiInternalAPI::class)
override fun getQQ(@PositiveNumbers id: Long): QQ = getQQ(id.coerceAtLeastOrFail(0).toUInt())
override suspend fun getGroup(internalId: GroupInternalId): Group = getGroup(internalId.toId())
@UseExperimental(MiraiInternalAPI::class, ExperimentalUnsignedTypes::class)
override suspend fun getGroup(id: GroupId): Group = groups.delegate.getOrNull(id.value) ?: inline {
val info: RawGroupInfo = try {
when (val response =
withSession { GroupPacket.QueryGroupInfo(qqAccount, id.toInternalId(), sessionKey).sendAndExpect<GroupPacket.InfoResponse>() }) {
is RawGroupInfo -> response
is GroupNotFound -> throw GroupNotFoundException("id=${id.value.toLong()}")
else -> assertUnreachable()
}
} catch (e: Exception) {
throw IllegalStateException("Cannot obtain group info for id ${id.value.toLong()}", e)
}
return groups.delegate.getOrAdd(id.value) { Group(this, id, info, coroutineContext) }
}
@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) }
override fun close(throwable: Throwable?) {
if (throwable == null) {
network.close()
this.supervisorJob.complete()
groups.delegate.clear()
qqs.delegate.clear()
} else {
network.close(throwable)
this.supervisorJob.completeExceptionally(throwable)
groups.delegate.clear()
qqs.delegate.clear()
}
}
// endregion
@UseExperimental(MiraiInternalAPI::class)
override fun close() {
_network.close()
this.supervisorJob.complete()
groups.delegate.clear()
qqs.delegate.clear()
}
}
......@@ -3,15 +3,9 @@
package net.mamoe.mirai.contact
import net.mamoe.mirai.Bot
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.chain
import net.mamoe.mirai.message.singleChain
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.joinToString
import net.mamoe.mirai.withSession
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.WeakRefProperty
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
......@@ -26,12 +20,13 @@ interface Contact {
/**
* 这个联系人所属 [Bot]
*/
val bot: Bot
@WeakRefProperty
val bot: Bot // weak ref
/**
* 可以是 QQ 号码或者群号码 [GroupId].
*/
val id: UInt
val id: Long
/**
* 向这个对象发送消息.
......@@ -39,6 +34,8 @@ interface Contact {
* 速度太快会被服务器屏蔽(无响应). 在测试中不延迟地发送 6 条消息就会被屏蔽之后的数据包 1 秒左右.
*/
suspend fun sendMessage(message: MessageChain)
suspend fun uploadImage(image: ExternalImage): ImageId
}
suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.chain())
......@@ -46,13 +43,13 @@ suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.c
suspend inline fun Contact.sendMessage(plain: String) = sendMessage(plain.singleChain())
/**
* 以 [BotSession] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值.
* 这个方法将能帮助使用在 [BotSession] 中定义的一些扩展方法, 如 [BotSession.sendAndExpectAsync]
* 以 [Bot] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值.
* 这个方法将能帮助使用在 [Bot] 中定义的一些扩展方法
*/
@UseExperimental(ExperimentalContracts::class)
inline fun <R> Contact.withSession(block: BotSession.() -> R): R {
inline fun <R> Contact.withBot(block: Bot.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return bot.withSession(block)
return bot.run(block)
}
......@@ -12,7 +12,7 @@ import net.mamoe.mirai.utils.joinToString
*/
@UseExperimental(MiraiInternalAPI::class)
@Suppress("unused")
class ContactList<C : Contact>(@PublishedApi internal val delegate: LockFreeLinkedList<C>) {
class ContactList<C : Contact>(@MiraiInternalAPI val delegate: LockFreeLinkedList<C>) {
/**
* ID 列表的字符串表示.
* 如:
......@@ -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) + "]"
operator fun get(id: UInt): C = delegate[id]
fun getOrNull(id: UInt): C? = delegate.getOrNull(id)
fun containsId(id: UInt): Boolean = delegate.getOrNull(id) != null
operator fun get(id: Long): C = delegate[id]
fun getOrNull(id: Long): C? = delegate.getOrNull(id)
fun containsId(id: Long): Boolean = delegate.getOrNull(id) != null
val size: Int get() = delegate.size
operator fun contains(element: C): Boolean = delegate.contains(element)
......@@ -35,14 +35,14 @@ class ContactList<C : Contact>(@PublishedApi internal val delegate: LockFreeLink
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 }
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 }
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 @@
package net.mamoe.mirai.contact
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.network.protocol.timpc.packet.action.GroupInfo
import net.mamoe.mirai.network.protocol.timpc.packet.action.QuitGroupResponse
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.internal.PositiveNumbers
import net.mamoe.mirai.network.data.GroupInfo
import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
......@@ -53,7 +50,7 @@ interface Group : Contact, CoroutineScope/*, Map<UInt, Member>*/ { // TODO: 2019
/**
* 获取群成员. 若此 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
*
* @see QuitGroupResponse.isSuccess 判断是否成功
*/
suspend fun quit(): QuitGroupResponse
suspend fun quit(): Boolean
fun toFullString(): String = "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})"
}
......@@ -81,27 +78,19 @@ interface Group : Contact, CoroutineScope/*, Map<UInt, Member>*/ { // TODO: 2019
* @see GroupInternalId.toId 由 [GroupInternalId] 转换为 [GroupId]
* @see GroupId.toInternalId 由 [GroupId] 转换为 [GroupInternalId]
*/
inline class GroupId(inline val value: UInt)
/**
* 将 [this] 转为 [GroupId].
*/
@Suppress("NOTHING_TO_INLINE")
inline fun UInt.groupId(): GroupId = GroupId(this)
inline class GroupId(inline val value: Long)
/**
* 将 [this] 转为 [GroupInternalId].
*/
@Suppress("NOTHING_TO_INLINE")
inline fun UInt.groupInternalId(): GroupInternalId = GroupInternalId(this)
fun Long.groupInternalId(): GroupInternalId = GroupInternalId(this)
/**
* 将无符号整数格式的 [Long] 转为 [GroupId].
*
* 注: 在 Java 中常用 [Long] 来表示 [UInt]
*/
fun @receiver:PositiveNumbers Long.groupId(): GroupId =
GroupId(this.coerceAtLeastOrFail(0).toUInt())
fun Long.groupId(): GroupId = GroupId(this.coerceAtLeastOrFail(0))
/**
* 一些群 API 使用的 ID. 在使用时会特别注明
......@@ -111,4 +100,4 @@ fun @receiver:PositiveNumbers Long.groupId(): GroupId =
* @see GroupInternalId.toId 由 [GroupInternalId] 转换为 [GroupId]
* @see GroupId.toInternalId 由 [GroupId] 转换为 [GroupInternalId]
*/
inline class GroupInternalId(inline val value: UInt)
inline class GroupInternalId(inline val value: Long)
......@@ -6,7 +6,7 @@ import kotlin.math.pow
@Suppress("ObjectPropertyName")
private val `10EXP6` = 10.0.pow(6).toUInt()
private val `10EXP6` = 10.0.pow(6)
fun GroupId.toInternalId(): GroupInternalId {
......@@ -23,12 +23,12 @@ fun GroupId.toInternalId(): GroupInternalId {
in 1..10 -> plusLeft(202, 6)
in 11..19 -> plusLeft(469, 6)
in 20..66 -> plusLeft(208, 7)
in 67..156 -> plusLeft(1943, 6)
in 67..156 -> plusLeft(1943, 6)
in 157..209 -> plusLeft(199, 7)
in 210..309 -> plusLeft(389, 7)
in 310..499 -> plusLeft(349, 7)
else -> null
}?.toUInt() ?: this.value
}?.toLong() ?: this.value
)
}
......@@ -36,17 +36,17 @@ fun GroupInternalId.toId(): GroupId = with(value.toString()) {
if (value < `10EXP6`) {
return GroupId(value)
}
val left: UInt = this.dropLast(6).toUInt()
val left = this.dropLast(6).toLong()
return GroupId(
when (left.toInt()) {
in 203..212 -> ((left - 202u).toString() + this.takeLast(6).toInt().toString()).toUInt()
in 480..488 -> ((left - 469u).toString() + this.takeLast(6).toInt().toString()).toUInt()
in 2100..2146 -> ((left.toString().take(3).toUInt() - 208u).toString() + this.takeLast(7).toInt().toString()).toUInt()
in 2010..2099 -> ((left - 1943u).toString() + this.takeLast(6).toInt().toString()).toUInt()
in 2147..2199 -> ((left.toString().take(3).toUInt() - 199u).toString() + this.takeLast(7).toInt().toString()).toUInt()
in 4100..4199 -> ((left.toString().take(3).toUInt() - 389u).toString() + this.takeLast(7).toInt().toString()).toUInt()
in 3800..3989 -> ((left.toString().take(3).toUInt() - 349u).toString() + this.takeLast(7).toInt().toString()).toUInt()
in 203..212 -> ((left - 202).toString() + this.takeLast(6).toInt().toString()).toLong()
in 480..488 -> ((left - 469).toString() + this.takeLast(6).toInt().toString()).toLong()
in 2100..2146 -> ((left.toString().take(3).toLong() - 208).toString() + this.takeLast(7).toInt().toString()).toLong()
in 2010..2099 -> ((left - 1943).toString() + this.takeLast(6).toInt().toString()).toLong()
in 2147..2199 -> ((left.toString().take(3).toLong() - 199).toString() + this.takeLast(7).toInt().toString()).toLong()
in 4100..4199 -> ((left.toString().take(3).toLong() - 389).toString() + this.takeLast(7).toInt().toString()).toLong()
in 3800..3989 -> ((left.toString().take(3).toLong() - 349).toString() + this.takeLast(7).toInt().toString()).toLong()
else -> value
}
)
......
......@@ -4,12 +4,9 @@ package net.mamoe.mirai.contact
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.data.Profile
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.timpc.packet.action.AvatarLink
import net.mamoe.mirai.network.protocol.timpc.packet.action.FriendNameRemark
import net.mamoe.mirai.network.protocol.timpc.packet.action.PreviousNameList
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.network.data.FriendNameRemark
import net.mamoe.mirai.network.data.PreviousNameList
import net.mamoe.mirai.network.data.Profile
/**
* QQ 对象.
......
......@@ -6,11 +6,10 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.isAdministrator
import net.mamoe.mirai.contact.isOperator
import net.mamoe.mirai.contact.isOwner
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.any
import net.mamoe.mirai.network.protocol.timpc.packet.event.FriendMessage
import net.mamoe.mirai.network.protocol.timpc.packet.event.GroupMessage
import net.mamoe.mirai.network.protocol.timpc.packet.event.MessagePacket
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.MessagePacket
import net.mamoe.mirai.message.data.Message
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
......@@ -188,15 +187,9 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
* 如果是这个人发的消息, 就执行 [onEvent]. 消息可以是好友消息也可以是群消息
*/
@MessageDsl
suspend inline fun sentBy(qqId: UInt, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) =
suspend inline fun sentBy(qqId: Long, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) =
content({ sender.id == qqId }, onEvent)
/**
* 如果是这个人发的消息, 就执行 [onEvent]. 消息可以是好友消息也可以是群消息
*/
@MessageDsl
suspend inline fun sentBy(qqId: Long, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = sentBy(qqId.toUInt(), onEvent)
/**
* 如果是管理员或群主发的消息, 就执行 [onEvent]
*/
......@@ -222,21 +215,15 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
* 如果是来自这个群的消息, 就执行 [onEvent]
*/
@MessageDsl
suspend inline fun sentFrom(id: UInt, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) =
suspend inline fun sentFrom(id: Long, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) =
content({ if (this is GroupMessage) group.id == id else false }, onEvent)
/**
* 如果是来自这个群的消息, 就执行 [onEvent]
*/
@MessageDsl
suspend inline fun sentFrom(id: Long, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = sentFrom(id.toUInt(), onEvent)
/**
* 如果消息内容包含 [M] 类型的 [Message], 就执行 [onEvent]
*/
@MessageDsl
suspend inline fun <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`
......@@ -283,7 +270,7 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
*/
@MessageDsl
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
executeAndReply(replier)
}
......
......@@ -7,7 +7,6 @@ import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.event.internal.HandlerWithSession
import net.mamoe.mirai.event.internal.Listener
import net.mamoe.mirai.event.internal.subscribeInternal
import net.mamoe.mirai.network.BotSession
import kotlin.reflect.KClass
/**
......@@ -19,88 +18,87 @@ import kotlin.reflect.KClass
// region 顶层方法
suspend inline fun <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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
// endregion
// region KClass 的扩展方法 (仅内部使用)
@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))
@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 })
@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 })
@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 })
@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)
@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)
@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)
@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 })
@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)
@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)
@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)
// 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
import kotlinx.coroutines.*
import kotlinx.coroutines.CompletableJob
import kotlinx.coroutines.Job
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.EventDebugLogger
import net.mamoe.mirai.event.ListeningStatus
import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.session
import net.mamoe.mirai.utils.internal.inlinedRemoveIf
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.io.logStacktrace
import net.mamoe.mirai.utils.unsafeWeakRef
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
import kotlin.jvm.JvmField
......@@ -24,8 +24,6 @@ import kotlin.reflect.KFunction
*/
var EventDisabled = false
// TODO: 2019/11/29 修改监听为 lock-free 模式
/**
* 监听和广播实现.
* 它会首先检查这个事件是否正在被广播
......@@ -34,38 +32,9 @@ var EventDisabled = false
*
* @author Him188moe
*/ // inline to avoid a Continuation creation
internal suspend inline fun <L : Listener<E>, E : Subscribable> KClass<E>.subscribeInternal(listener: L): L = with(this.listeners()) {
if (mainMutex.tryLock(listener)) {//能锁则代表这个事件目前没有正在广播.
try {
add(listener)//直接修改主监听者列表
EventDebugLogger.debug("Added a listener to ${this@subscribeInternal.simpleName}")
} finally {
mainMutex.unlock(listener)
}
return listener
}
//不能锁住, 则这个事件正在广播, 那么要将新的监听者放入缓存
cacheMutex.withLock {
cache.add(listener)
EventDebugLogger.debug("Added a listener to cache of ${this@subscribeInternal.simpleName}")
}
GlobalScope.launch {
//启动协程并等待正在进行的广播结束, 然后将缓存转移到主监听者列表
//启动后的协程马上就会因为锁而被挂起
mainMutex.withLock(listener) {
cacheMutex.withLock {
if (cache.size != 0) {
addAll(cache)
cache.clear()
EventDebugLogger.debug("Cache of ${this@subscribeInternal.simpleName} is now transferred to main")
}
}
}
}
return@with listener
internal suspend inline fun <L : Listener<E>, E : Subscribable> KClass<E>.subscribeInternal(listener: L): L {
this.listeners().addLast(listener)
return listener
}
/**
......@@ -77,8 +46,6 @@ internal suspend inline fun <L : Listener<E>, E : Subscribable> KClass<E>.subscr
* @author Him188moe
*/
sealed class Listener<in E : Subscribable> : CompletableJob {
@JvmField
internal val lock = Mutex()
abstract suspend fun onEvent(event: E): ListeningStatus
}
......@@ -113,7 +80,7 @@ internal class Handler<in E : Subscribable>
@Suppress("FunctionName")
internal suspend inline fun <E : Subscribable> HandlerWithSession(
bot: Bot,
noinline handler: suspend BotSession.(E) -> ListeningStatus
noinline handler: suspend Bot.(E) -> ListeningStatus
): HandlerWithSession<E> {
return HandlerWithSession(bot, coroutineContext[Job], coroutineContext, handler)
}
......@@ -125,10 +92,10 @@ internal suspend inline fun <E : Subscribable> HandlerWithSession(
*/
@PublishedApi
internal class HandlerWithSession<E : Subscribable> @PublishedApi internal constructor(
@JvmField val bot: Bot,
parentJob: Job?, private val context: CoroutineContext, @JvmField val handler: suspend BotSession.(E) -> ListeningStatus
) :
Listener<E>(), CompletableJob by Job(parentJob) {
bot: Bot,
parentJob: Job?, private val context: CoroutineContext, @JvmField val handler: suspend Bot.(E) -> ListeningStatus
) : Listener<E>(), CompletableJob by Job(parentJob) {
val bot: Bot by bot.unsafeWeakRef()
override suspend fun onEvent(event: E): ListeningStatus {
if (isCompleted || isCancelled) return ListeningStatus.STOPPED
......@@ -137,10 +104,11 @@ internal class HandlerWithSession<E : Subscribable> @PublishedApi internal const
if (event !is BotEvent || event.bot !== bot) return ListeningStatus.LISTENING
return try {
withContext(context) { bot.session.handler(event) }.also { if (it == ListeningStatus.STOPPED) complete() }
withContext(context) { bot.handler(event) }.also { if (it == ListeningStatus.STOPPED) complete() }
} catch (e: Throwable) {
e.logStacktrace()
//completeExceptionally(e)
complete()
ListeningStatus.STOPPED
}
}
......@@ -151,24 +119,7 @@ internal class HandlerWithSession<E : Subscribable> @PublishedApi internal const
*/
internal suspend fun <E : Subscribable> KClass<out E>.listeners(): EventListeners<E> = EventListenerManger.get(this)
internal class EventListeners<E : Subscribable> : MutableList<Listener<E>> by mutableListOf() {
/**
* 主监听者列表.
* 广播事件时使用这个锁.
*/
@JvmField
val mainMutex = Mutex()
/**
* 缓存(监听)事件时使用的锁
*/
@JvmField
val cacheMutex = Mutex()
/**
* 等待加入到主 list 的监听者. 务必使用 [cacheMutex]
*/
@JvmField
val cache: MutableList<Listener<E>> = mutableListOf()
internal class EventListeners<E : Subscribable> : LockFreeLinkedList<Listener<E>>() {
init {
this::class.members.filterIsInstance<KFunction<*>>().forEach {
if (it.name == "add") {
......@@ -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
callListeners(this::class.listeners())
applySuperListeners(this::class) { callListeners(it) }
return this
}
callAndRemoveIfRequired(this::class.listeners())
private suspend inline fun <E : Subscribable> E.callListeners(listeners: EventListeners<in E>) {
//自己持有, 则是在一个事件中
if (listeners.mainMutex.holdsLock(listeners)) {
callAndRemoveIfRequired(listeners)
} else {
while (!listeners.mainMutex.tryLock(listeners)) {
delay(10)
}
try {
callAndRemoveIfRequired(listeners)
} finally {
listeners.mainMutex.unlock(listeners)
this::class.supertypes.forEach { superType ->
if (Subscribable::class.isInstance(superType)) {
// the super type is a child of Subscribable, then we can cast.
@Suppress("UNCHECKED_CAST")
callAndRemoveIfRequired((superType.classifier as KClass<out Subscribable>).listeners())
}
}
return this
}
private suspend inline fun <E : Subscribable> E.callAndRemoveIfRequired(listeners: EventListeners<in E>) = listeners.inlinedRemoveIf {
if (it.lock.tryLock()) {
try {
it.onEvent(this) == ListeningStatus.STOPPED
} finally {
it.lock.unlock()
private suspend inline fun <E : Subscribable> E.callAndRemoveIfRequired(listeners: EventListeners<E>) {
listeners.forEach {
if (it.onEvent(this) == ListeningStatus.STOPPED) {
listeners.remove(it) // atomic remove
}
} 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")
package net.mamoe.mirai.network.protocol.timpc.packet.event
package net.mamoe.mirai.message
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.String
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.getGroup
import net.mamoe.mirai.message.*
import net.mamoe.mirai.message.internal.readMessageChain
import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion
import net.mamoe.mirai.network.protocol.timpc.packet.action.ImageLink
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.network.data.EventPacket
import net.mamoe.mirai.network.data.ImageLink
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
import net.mamoe.mirai.utils.io.printTLVMap
import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.readTLVMap
import net.mamoe.mirai.utils.io.readUShortLVByteArray
import net.mamoe.mirai.withSession
import kotlin.jvm.JvmName
/**
* 平台相关扩展
*/
@UseExperimental(MiraiInternalAPI::class)
expect abstract class MessagePacket<TSender : QQ, TSubject : Contact>() : MessagePacketBase<TSender, TSubject>
expect abstract class MessagePacket<TSender : QQ, TSubject : Contact>(bot: Bot) : MessagePacketBase<TSender, TSubject>
@MiraiInternalAPI
abstract class MessagePacketBase<TSender : QQ, TSubject : Contact> : EventPacket, BotEvent() {
internal lateinit var botVar: Bot
override val bot: Bot get() = botVar
abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) : EventPacket, BotEvent() {
override val bot: Bot by _bot.unsafeWeakRef()
/**
* 消息事件主体.
......@@ -86,7 +73,7 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact> : EventPacket
// endregion
// 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.download(): ByteReadPacket = getLink().download()
......@@ -94,137 +81,11 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact> : EventPacket
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 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 UInt.group(): Group = bot.getGroup(GroupId(this))
suspend inline fun GroupId.group(): Group = bot.getGroup(this)
suspend inline fun GroupInternalId.group(): Group = bot.getGroup(this)
}
// region group message
@Suppress("unused", "NOTHING_TO_INLINE")
data class GroupMessage(
val group: Group,
val senderName: String,
/**
* 发送方权限.
*/
val permission: MemberPermission,
override val sender: Member,
override val message: MessageChain
) : MessagePacket<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
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.message
package net.mamoe.mirai.message.data
import net.mamoe.mirai.contact.QQ
......@@ -8,7 +8,7 @@ import net.mamoe.mirai.contact.QQ
/**
* At 一个人. 只能发送给一个群.
*/
inline class At(val target: UInt) : Message {
inline class At(val target: Long) : Message {
constructor(target: QQ) : this(target.id)
override val stringValue: String get() = "[@$target]" // TODO: 2019/11/25 使用群名称进行 at. 因为手机端只会显示这个文字
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.message
package net.mamoe.mirai.message.data
import kotlin.jvm.JvmStatic
/**
* QQ 自带表情
*/
inline class Face(val id: FaceId) : Message {
override val stringValue: String get() = "[face${id.value}]"
override fun toString(): String = stringValue
companion object Key : Message.Key<Face>
}
/**
* @author LamGC
*/
@Suppress("SpellCheckingInspection", "unused")
inline class FaceId(inline val value: UByte) {
@UseExperimental(ExperimentalUnsignedTypes::class)
inline class FaceId constructor(inline val value: UByte) {
companion object {
@JvmStatic
val unknown: FaceId = FaceId(0xffu)
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.message
package net.mamoe.mirai.message.data
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.network.protocol.timpc.packet.action.FriendImagePacket
import net.mamoe.mirai.utils.ExternalImage
......@@ -30,7 +29,8 @@ inline class ImageId0x06(override inline val value: String) : ImageId {
/**
* 一般是群的图片的 id.
*/
class ImageId0x03 constructor(override inline val value: String, inline val uniqueId: UInt, inline val height: Int, inline val width: Int) : ImageId {
class ImageId0x03 constructor(override inline val value: String, inline val uniqueId: UInt, inline val height: Int, inline val width: Int) :
ImageId {
override fun toString(): String = "ImageId(value=$value, uniqueId=${uniqueId}, height=$height, width=$width)"
val md5: ByteArray
......@@ -46,7 +46,8 @@ class ImageId0x03 constructor(override inline val value: String, inline val uniq
inline fun ImageId(value: String): ImageId = ImageId0x06(value)
@Suppress("FunctionName", "NOTHING_TO_INLINE")
inline fun ImageId(value: String, uniqueId: UInt, height: Int, width: Int): ImageId = ImageId0x03(value, uniqueId, height, width)
inline fun ImageId(value: String, uniqueId: UInt, height: Int, width: Int): ImageId =
ImageId0x03(value, uniqueId, height, width)
/**
......@@ -65,6 +66,7 @@ fun ImageId.checkLength() = check(value.length == 37 || value.length == 42) { "I
fun ImageId.requireLength() = require(value.length == 37 || value.length == 42) { "Illegal ImageId length" }
@Suppress("NOTHING_TO_INLINE")
inline fun ImageId.image(): Image = Image(this)
inline fun ImageId.image(): Image =
Image(this)
suspend inline fun ImageId.sendTo(contact: Contact) = contact.sendMessage(this.image())
@file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.message
package net.mamoe.mirai.message.data
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.sendMessage
......
package net.mamoe.mirai.message
package net.mamoe.mirai.message.data
import net.mamoe.mirai.message.data.NullMessageChain.toString
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
......@@ -75,7 +76,8 @@ fun MessageChain(vararg messages: Message): MessageChain =
* 构造 [MessageChain]
*/
@Suppress("FunctionName")
fun MessageChain(messages: Iterable<Message>): MessageChain = MessageChainImpl(messages.toMutableList())
fun MessageChain(messages: Iterable<Message>): MessageChain =
MessageChainImpl(messages.toMutableList())
/**
* 构造单元素的不可修改的 [MessageChain]. 内部类实现为 [SingleMessageChain]
......@@ -106,13 +108,16 @@ fun SingleMessageChain(delegate: Message): MessageChain {
* 否则将调用 [MessageChain] 构造一个 [MessageChainImpl]
*/
@Suppress("NOTHING_TO_INLINE")
inline fun Message.chain(): MessageChain = if (this is MessageChain) this else MessageChain(this)
inline fun Message.chain(): MessageChain = if (this is MessageChain) this else MessageChain(
this
)
/**
* 构造 [MessageChain]
*/
@Suppress("unused", "NOTHING_TO_INLINE")
inline fun List<Message>.messageChain(): MessageChain = MessageChain(this)
inline fun List<Message>.messageChain(): MessageChain =
MessageChain(this)
/**
......@@ -230,51 +235,45 @@ class EmptyMessageChain : MessageChain {
* Null 的 [MessageChain].
* 它不包含任何元素, 也没有创建任何 list.
*
* - 所有 get 方法均抛出 [IndexOutOfBoundsException]
* - 所有 add 方法均抛出 [UnsupportedOperationException]
* - 其他判断类方法均 false 或 -1
* 除 [toString] 外, 其他方法均 [error]
*/
object NullMessageChain : MessageChain {
override fun subList(fromIndex: Int, toIndex: Int): MutableList<Message> = unsupported()
override fun subList(fromIndex: Int, toIndex: Int): MutableList<Message> = error("accessing NullMessageChain")
override val stringValue: String
get() = ""
override val stringValue: String get() = "null"
override fun toString(): String = stringValue
override fun toString(): String = "null"
override fun contains(sub: String): Boolean = false
override fun contains(element: Message): Boolean = false
override fun followedBy(tail: Message): MessageChain =
MessageChainImpl(tail)
override 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 contains(sub: String): Boolean = error("accessing NullMessageChain")
override fun contains(element: Message): Boolean = error("accessing NullMessageChain")
override fun followedBy(tail: Message): MessageChain = error("accessing NullMessageChain")
override fun listIterator(index: Int): MutableListIterator<Message> =
throw IndexOutOfBoundsException(index.toString())
override fun remove(element: Message): Boolean = false
override fun removeAll(elements: Collection<Message>): Boolean = false
override fun removeAt(index: Int): Message = throw IndexOutOfBoundsException(index.toString())
override fun retainAll(elements: Collection<Message>): Boolean = false
override fun set(index: Int, element: Message): Message = throw IndexOutOfBoundsException(index.toString())
private fun unsupported(): Nothing = throw UnsupportedOperationException()
override val size: Int get() = error("accessing NullMessageChain")
override fun containsAll(elements: Collection<Message>): Boolean = error("accessing NullMessageChain")
override fun get(index: Int): Message = error("accessing NullMessageChain")
override fun indexOf(element: Message): Int = error("accessing NullMessageChain")
override fun isEmpty(): Boolean = error("accessing NullMessageChain")
override fun iterator(): MutableIterator<Message> = error("accessing NullMessageChain")
override fun lastIndexOf(element: Message): Int = error("accessing NullMessageChain")
override fun add(element: Message): Boolean = error("accessing NullMessageChain")
override fun add(index: Int, element: Message) = error("accessing NullMessageChain")
override fun addAll(index: Int, elements: Collection<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 {
......
@file:Suppress("MemberVisibilityCanBePrivate")
package net.mamoe.mirai.message
package net.mamoe.mirai.message.data
/**
* XML 消息, 如分享, 卡片等.
*
* @see buildXMLMessage
*/
inline class XMLMessage(override val stringValue: String) : Message, SingleOnly {
inline class XMLMessage(override val stringValue: String) : Message,
SingleOnly {
override fun followedBy(tail: Message): Nothing = error("XMLMessage Message cannot be followed")
override fun toString(): String = stringValue
}
......@@ -16,7 +17,8 @@ inline class XMLMessage(override val stringValue: String) : Message, SingleOnly
* 构造一条 XML 消息
*/
@XMLDsl
inline fun buildXMLMessage(block: @XMLDsl XMLMessageBuilder.() -> Unit): XMLMessage = XMLMessage(XMLMessageBuilder().apply(block).text)
inline fun buildXMLMessage(block: @XMLDsl XMLMessageBuilder.() -> Unit): XMLMessage =
XMLMessage(XMLMessageBuilder().apply(block).text)
@Suppress("NOTHING_TO_INLINE")
@XMLDsl
......
......@@ -3,7 +3,8 @@
package net.mamoe.mirai.message.internal
import kotlinx.io.core.*
import net.mamoe.mirai.message.*
import net.mamoe.mirai.message.MessageType
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.unzip
......@@ -27,7 +28,7 @@ internal fun IoBuffer.parsePlainTextOrAt(): Message {
PlainText(msg)
} else {
discardExact(10)
At(readUInt())
At(readQQ())
}
}
......@@ -38,13 +39,13 @@ internal fun IoBuffer.parseLongText0x19(): PlainText {
//AA 02 33 50 00 60 00 68 00 9A 01 2A 08 09 20 CB 50 78 80 80 04 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 D3 02 A0 03 10 B0 03 00 C0 03 AF 9C 01 D0 03 00 E8 03 00
//AA 02 30 50 00 60 00 68 00 9A 01 27 08 0A 78 A7 C0 04 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 00 B0 03 00 C0 03 00 D0 03 00 E8 03 00
// 应该是手机发送时的字体或气泡之类的
// println("parseLongText0x19.raw=${raw.toUHexString()}")
// println("parseLongText0x19.raw=${raw.toHexString()}")
return PlainText("")
}
internal fun IoBuffer.parseMessageImage0x06(): Image {
discardExact(1)
//MiraiLogger.debug(this.toUHexString())
//MiraiLogger.debug(this.toHexString())
val filenameLength = readShort()
discardExact(filenameLength.toInt())
......@@ -199,7 +200,7 @@ internal fun ByteReadPacket.readMessage(): Message? {
}
}
internal fun ByteReadPacket.readMessageChain(): MessageChain {
fun ByteReadPacket.readMessageChain(): MessageChain {
val chain = MessageChain()
do {
if (this.remaining == 0L) {
......@@ -209,7 +210,7 @@ internal fun ByteReadPacket.readMessageChain(): MessageChain {
return chain
}
internal fun MessageChain.toPacket(): ByteReadPacket = buildPacket {
fun MessageChain.toPacket(): ByteReadPacket = buildPacket {
this@toPacket.forEach { message ->
writePacket(with(message) {
when (this) {
......@@ -244,7 +245,7 @@ internal fun MessageChain.toPacket(): ByteReadPacket = buildPacket {
writeShortLVString(stringValue) // 这个应该是 "@群名", 手机上面会显示这个消息, 电脑会显示下面那个
// 06 00 0D 00 01 00 00 00 08 00 76 E4 B8 DD 00 00
writeHex("06 00 0D 00 01 00 00 00 08 00")
writeUInt(target)
writeQQ(target)
writeZero(2)
}
}
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableJob
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.timpc.handler.DataPacketSocketAdapter
import net.mamoe.mirai.network.protocol.timpc.handler.TemporaryPacketHandler
import net.mamoe.mirai.network.protocol.timpc.packet.OutgoingPacket
import net.mamoe.mirai.network.protocol.timpc.packet.Packet
import net.mamoe.mirai.network.protocol.timpc.packet.login.HeartbeatPacket
import net.mamoe.mirai.network.protocol.timpc.packet.login.RequestSKeyPacket
import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.PlatformDatagramChannel
/**
......@@ -33,29 +27,41 @@ import net.mamoe.mirai.utils.io.PlatformDatagramChannel
* A BotNetworkHandler is used to connect with Tencent servers.
*/
@Suppress("PropertyName")
interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : CoroutineScope {
val socket: Socket
val bot: Bot
val supervisor: CompletableJob
abstract class BotNetworkHandler : CoroutineScope {
abstract val bot: Bot
val session: BotSession
abstract val supervisor: CompletableJob
/**
* 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回登录结果
* 本函数将挂起直到登录成功.
*/
suspend fun login(): LoginResult
abstract suspend fun login()
/**
* 等待直到与服务器断开连接. 若未连接则立即返回
*/
suspend fun awaitDisconnection()
abstract suspend fun awaitDisconnection()
/**
* 关闭网络接口, 停止所有有关协程和任务
*/
fun close(cause: Throwable? = null) {
open fun close(cause: Throwable? = null) {
supervisor.cancel(CancellationException("handler closed", cause))
}
/*
@PublishedApi
internal abstract fun CoroutineScope.QQ(bot: Bot, id: Long, coroutineContext: CoroutineContext): QQ
@PublishedApi
internal abstract fun CoroutineScope.Group(bot: Bot, groupId: GroupId, info: RawGroupInfo, context: CoroutineContext): Group
@PublishedApi
internal abstract fun Group.Member(delegate: QQ, permission: MemberPermission, coroutineContext: CoroutineContext): Member
*/
}
\ No newline at end of file
@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.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")
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 kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* 登录结果. 除 [SUCCESS] 外均为失败.
* @see LoginResult.requireSuccess 要求成功
* 登录失败的原因
*/
enum class LoginResult(val id: Byte) {
/**
* 登录成功
*/
SUCCESS(0),
/**
* 密码错误
*/
......@@ -72,53 +62,4 @@ enum class LoginResult(val id: Byte) {
* 该号码长期未登录, 为了保证账号安全, 已被系统设置成保护状态, 请用手机 TIM 最新版本登录, 登录成功后即可自动解除保护模式
*/ // TIM的错误代码为 00020
PROTECTED(11),
}
/**
* 如果 [this] 不为 [LoginResult.SUCCESS] 就抛出消息为 [lazyMessage] 的 [IllegalStateException]
*/
@UseExperimental(ExperimentalContracts::class)
inline fun LoginResult.requireSuccess(lazyMessage: (LoginResult) -> String) {
contract {
callsInPlace(lazyMessage, InvocationKind.AT_MOST_ONCE)
}
if (this != SUCCESS) error(lazyMessage(this))
}
/**
* 检查 [this] 为 [LoginResult.SUCCESS].
* 失败则 [error]
*/
@Suppress("NOTHING_TO_INLINE")
inline fun LoginResult.requireSuccess() = requireSuccess { "Login failed: $this" }
/**
* 检查 [this] 为 [LoginResult.SUCCESS].
* 失败则返回 `null`
*
* @return 成功时 [Unit], 失败时 `null`
*/
@Suppress("NOTHING_TO_INLINE")
inline fun LoginResult.requireSuccessOrNull(): Unit? = if (this == SUCCESS) Unit else null
/**
* 返回 [this] 是否为 [LoginResult.SUCCESS].
*/
@Suppress("NOTHING_TO_INLINE")
@UseExperimental(ExperimentalContracts::class)
inline fun LoginResult.isSuccess(): Boolean = this == SUCCESS
/**
* 检查 [this] 为 [LoginResult.SUCCESS].
* 失败则返回 `null`
*
* @return 成功时 [Unit], 失败时 `null`
*/
@UseExperimental(ExperimentalContracts::class)
inline fun <R> LoginResult.ifFail(block: (LoginResult) -> R): R? {
contract {
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
}
return if (this != SUCCESS) block(this) else null
}
}
\ No newline at end of file
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")
package net.mamoe.mirai.contact.data
package net.mamoe.mirai.network.data
import io.ktor.util.date.GMTDate
......@@ -9,7 +9,7 @@ import io.ktor.util.date.GMTDate
*/
@Suppress("PropertyName")
data class Profile(
val qq: UInt,
val qq: Long,
val nickname: String,
val englishName: String?,
val chineseName: String?,
......
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.*
* 非常不建议在发行版本中使用这些 API.
*/
@Experimental(level = Experimental.Level.ERROR)
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
@Target(
CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR,
CLASS,
FUNCTION,
PROPERTY
)
annotation class MiraiInternalAPI
/**
......
......@@ -2,7 +2,6 @@ package net.mamoe.mirai.utils
import kotlinx.io.core.IoBuffer
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.timpc.packet.login.TouchPacket.TouchResponse
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
import kotlin.jvm.JvmStatic
......@@ -24,9 +23,9 @@ expect var DefaultCaptchaSolver: CaptchaSolver
*/
class BotConfiguration : CoroutineContext.Element {
/**
* 等待 [TouchResponse] 的时间
* 连接每个服务器的时间
*/
var touchTimeoutMillis: Long = 2.secondsToMillis
var touchTimeoutMillis: Long = 1.secondsToMillis
/**
* 是否使用随机的设备名.
* 使用随机可以降低被封禁的风险, 但可能导致每次登录都需要输入验证码
......
......@@ -7,7 +7,7 @@ import net.mamoe.mirai.utils.io.getRandomByteArray
private const val GTK_BASE_VALUE: Int = 5381
internal fun getGTK(sKey: String): Int {
fun getGTK(sKey: String): Int {
var value = GTK_BASE_VALUE
for (c in sKey.toByteArray()) {
value += (value shl 5) + c.toInt()
......@@ -18,14 +18,11 @@ internal fun getGTK(sKey: String): Int {
}
@Tested
@PublishedApi
internal fun BytePacketBuilder.writeCRC32() = writeCRC32(getRandomByteArray(16))
fun BytePacketBuilder.writeCRC32() = writeCRC32(getRandomByteArray(16))
@PublishedApi
internal fun BytePacketBuilder.writeCRC32(key: ByteArray) {
fun BytePacketBuilder.writeCRC32(key: ByteArray) {
writeFully(key)//key
writeInt(crc32(key))
}
@PublishedApi
internal fun md5(str: String): ByteArray = md5(str.toByteArray())
\ No newline at end of file
fun md5(str: String): ByteArray = md5(str.toByteArray())
\ No newline at end of file
......@@ -7,8 +7,7 @@ import kotlinx.io.core.Input
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.*
import net.mamoe.mirai.network.protocol.timpc.packet.action.uploadImage
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.io.toUHexString
@Suppress("FunctionName")
......@@ -50,7 +49,14 @@ class ExternalImage(
* 用于发送消息的 [ImageId]
*/
@Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
val groupImageId: ImageId by lazy { ImageId0x03("{${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}}.$format", 0u, height, width) }
val groupImageId: ImageId by lazy {
ImageId0x03(
"{${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}}.$format",
0u,
height,
width
)
}
override fun toString(): String = "[ExternalImage(${width}x$height $format)]"
}
......
@file:Suppress("NOTHING_TO_INLINE")
@file:Suppress("NOTHING_TO_INLINE", "unused")
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
* QQ 在线状态
*
* @author Him188moe
* @see net.mamoe.mirai.network.protocol.timpc.packet.login.ChangeOnlineStatusPacket
* @see net.mamoe.mirai.timpc.network.packet.login.ChangeOnlineStatusPacket
*/
inline class OnlineStatus(
inline val id: UByte
......
package net.mamoe.mirai.utils
/**
* 图片文件过大
*/
class OverFileSizeMaxException : IllegalStateException()
......@@ -4,7 +4,6 @@ package net.mamoe.mirai.utils
import io.ktor.client.HttpClient
import io.ktor.util.date.GMTDate
import kotlinx.io.core.IoBuffer
/**
* 时间戳
......@@ -44,5 +43,4 @@ expect fun localIpAddress(): String
/**
* Ktor HttpClient. 不同平台使用不同引擎.
*/
@PublishedApi
internal expect val Http: HttpClient
expect val Http: HttpClient
......@@ -10,9 +10,13 @@ import kotlin.coroutines.CoroutineContext
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)
/**
......
......@@ -3,8 +3,6 @@ package net.mamoe.mirai.utils
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.IoBuffer
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.network.protocol.timpc.packet.Decrypter
import net.mamoe.mirai.network.protocol.timpc.packet.DecrypterByteArray
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.toByteArray
import net.mamoe.mirai.utils.io.toUHexString
......@@ -17,7 +15,7 @@ import kotlin.random.Random
/**
* 解密错误
*/
internal class DecryptionFailedException : Exception {
class DecryptionFailedException : Exception {
constructor() : super()
constructor(message: String?) : super(message)
}
......@@ -31,9 +29,7 @@ internal class DecryptionFailedException : Exception {
* @param key 长度至少为 16
* @throws DecryptionFailedException 解密错误时
*/
internal fun ByteArray.encryptBy(key: ByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key, sourceLength = length)
internal fun ByteArray.encryptBy(key: DecrypterByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key.value, sourceLength = length)
fun ByteArray.encryptBy(key: ByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key, sourceLength = length)
/**
* 在 [ByteArrayPool] 缓存 [this], 然后使用 [key] 加密.
......@@ -42,7 +38,7 @@ internal fun ByteArray.encryptBy(key: DecrypterByteArray, length: Int = this.siz
* @consumer 由于缓存需要被回收, 需在方法内执行解密后明文的消耗过程
* @throws DecryptionFailedException 解密错误时
*/
internal inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, length: Int = remaining.toInt() - offset, consumer: (ByteArray) -> Unit) {
inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, length: Int = remaining.toInt() - offset, consumer: (ByteArray) -> Unit) {
ByteArrayPool.useInstance {
this.readFully(it, offset, length)
consumer(it.encryptBy(key, length = length))
......@@ -60,7 +56,7 @@ internal inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, le
* @param key 固定长度 16
* @throws DecryptionFailedException 解密错误时
*/
internal fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteArray =
fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteArray =
TEA.decrypt(checkDataLengthAndReturnSelf(length), key, sourceLength = length)
/**
......@@ -71,7 +67,7 @@ internal fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteA
* @param key 长度至少为 16
* @throws DecryptionFailedException 解密错误时
*/
internal fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteArray {
fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteArray {
checkDataLengthAndReturnSelf(length)
return ByteArrayPool.useInstance { keyBuffer ->
key.readFully(keyBuffer, 0, key.readRemaining)
......@@ -85,7 +81,7 @@ internal fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteAr
* @param key 长度至少为 16
* @throws DecryptionFailedException 解密错误时
*/
internal fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemaining - offset): ByteArray {
fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemaining - offset): ByteArray {
return ByteArrayPool.useInstance {
this.readFully(it, offset, length)
it.checkDataLengthAndReturnSelf(length)
......@@ -97,20 +93,18 @@ internal fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = r
// region ByteReadPacket extension
internal fun ByteReadPacket.decryptBy(key: ByteArray): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
internal fun ByteReadPacket.decryptBy(key: IoBuffer): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
fun ByteReadPacket.decryptBy(key: ByteArray): 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 {
val length = remaining.toInt()
readFully(it, 0, length)
consumer(it.decryptBy(key, length))
}.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 {
val length = remaining.toInt()
readFully(it, 0, length)
......
......@@ -39,12 +39,26 @@ expect class WeakRef<T>(referent: T) {
fun clear()
}
/**
* Indicates that the property is delegated by a [WeakRef]
*
* @see weakRef
*/
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.SOURCE)
annotation class WeakRefProperty
/**
* Provides a weak reference to [this]
* The `getValue` for delegation returns [this] when [this] is not released by GC
*/
fun <T> 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].
* 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
* 要求 [this] 最小为 [min].
*/
@Suppress("NOTHING_TO_INLINE")
@PublishedApi
internal inline fun Int.coerceAtLeastOrFail(min: Int): Int {
inline fun Int.coerceAtLeastOrFail(min: Int): Int {
require(this >= min)
return this
}
......@@ -14,8 +13,7 @@ internal inline fun Int.coerceAtLeastOrFail(min: Int): Int {
* 要求 [this] 最小为 [min].
*/
@Suppress("NOTHING_TO_INLINE")
@PublishedApi
internal inline fun Long.coerceAtLeastOrFail(min: Long): Long {
inline fun Long.coerceAtLeastOrFail(min: Long): Long {
require(this >= min)
return this
}
......@@ -24,20 +22,11 @@ internal inline fun Long.coerceAtLeastOrFail(min: Long): Long {
* 要求 [this] 最大为 [max].
*/
@Suppress("NOTHING_TO_INLINE")
@PublishedApi
internal inline fun Int.coerceAtMostOrFail(max: Int): Int =
inline fun Int.coerceAtMostOrFail(max: Int): Int =
if (this >= max) error("value is greater than its expected maximum value $max")
else this
@Suppress("NOTHING_TO_INLINE")
@PublishedApi
internal inline fun Long.coerceAtMostOrFail(max: Long): Long =
inline fun Long.coerceAtMostOrFail(max: Long): Long =
if (this >= max) error("value is greater than its expected maximum value $max")
else this
/**
* 表示这个参数必须为正数. 仅用于警示
*/
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE)
internal annotation class PositiveNumbers
\ No newline at end of file
else this
\ No newline at end of file
......@@ -6,7 +6,7 @@ import kotlinx.io.pool.ObjectPool
internal const val DEFAULT_BYTE_ARRAY_POOL_SIZE = 256
internal const val DEFAULT_BYTE_ARRAY_SIZE = 4096
internal val ByteArrayPool: ObjectPool<ByteArray> = ByteArrayPoolImpl
val ByteArrayPool: ObjectPool<ByteArray> = ByteArrayPoolImpl
private object ByteArrayPoolImpl : DefaultPool<ByteArray>(DEFAULT_BYTE_ARRAY_POOL_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
......@@ -7,33 +7,41 @@ import kotlinx.io.charsets.Charsets
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.String
import kotlinx.io.core.use
import kotlin.jvm.JvmOverloads
import kotlin.jvm.JvmSynthetic
@JvmOverloads
fun ByteArray.toHexString(separator: String = " "): String = this.joinToString(separator) {
var ret = it.toString(16).toUpperCase()
if (ret.length == 1) {
ret = "0$ret"
@Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray
@UseExperimental(ExperimentalUnsignedTypes::class)
fun ByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String {
val lastIndex = offset + length
return buildString(length * 2) {
this@toUHexString.forEachIndexed { index, it ->
if (index in offset until lastIndex) {
var ret = it.toByte().toUByte().toString(16).toUpperCase()
if (ret.length == 1) ret = "0$ret"
append(ret)
if (index != lastIndex) append(separator)
}
}
}
return@joinToString ret
}
@JvmOverloads
fun ByteArray.toUHexString(separator: String = " "): String = this.toUByteArray().toUHexString(separator)
fun ByteArray.encodeToString(charset: Charset = Charsets.UTF_8): String = String(this, charset = charset)
@JvmSynthetic
@JvmOverloads
fun UByteArray.toUHexString(separator: String = " "): String = this.joinToString(separator) {
var ret = it.toString(16).toUpperCase()
if (ret.length == 1) {
ret = "0$ret"
@Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray
@ExperimentalUnsignedTypes
fun UByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String {
val lastIndex = offset + length
return buildString(length * 2) {
this@toUHexString.forEachIndexed { index, it ->
if (index in offset until lastIndex) {
var ret = it.toByte().toUByte().toString(16).toUpperCase()
if (ret.length == 1) ret = "0$ret"
append(ret)
if (index != lastIndex) append(separator)
}
}
}
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)
inline fun <R> ByteArray.read(t: ByteReadPacket.() -> R): R = this.toReadPacket().use(t)
......
package net.mamoe.mirai.utils.io
import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.internal.printColorize
import net.mamoe.mirai.utils.internal.printCompareHex
import net.mamoe.mirai.utils.withSwitch
internal object DebugLogger : MiraiLogger by DefaultLogger("Packet Debug").withSwitch()
object DebugLogger : MiraiLogger by DefaultLogger("Packet Debug").withSwitch()
internal fun Throwable.logStacktrace(message: String? = null) = DebugLogger.error(message, this)
fun Throwable.logStacktrace(message: String? = null) = DebugLogger.error(message, this)
@PublishedApi
internal fun debugPrintln(any: Any?) = DebugLogger.debug(any)
fun debugPrintln(any: Any?) = DebugLogger.debug(any)
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this"))
internal fun String.debugPrint(name: String): String {
fun String.debugPrint(name: String): String {
DebugLogger.debug("$name=$this")
return this
}
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this"))
internal fun ByteArray.debugPrint(name: String): ByteArray {
fun ByteArray.debugPrint(name: String): ByteArray {
DebugLogger.debug(name + "=" + this.toUHexString())
return this
}
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this"))
internal fun IoBuffer.debugPrint(name: String): IoBuffer {
val readBytes = this.readBytes()
DebugLogger.debug(name + "=" + readBytes.toUHexString())
return readBytes.toIoBuffer()
fun IoBuffer.debugPrint(name: String): IoBuffer {
ByteArrayPool.useInstance {
val count = this.readAvailable(it)
DebugLogger.debug(name + "=" + it.toUHexString(offset = 0, length = count))
return it.toIoBuffer(0, count)
}
}
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this"))
internal inline fun IoBuffer.debugCopyUse(block: IoBuffer.() -> Unit): IoBuffer {
val readBytes = this.readBytes()
block(readBytes.toIoBuffer())
return readBytes.toIoBuffer()
inline fun IoBuffer.debugCopyUse(block: IoBuffer.() -> Unit): IoBuffer {
ByteArrayPool.useInstance {
val count = this.readAvailable(it)
block(it.toIoBuffer(0, count))
return it.toIoBuffer(0, count)
}
}
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("discardExact(n)"))
internal fun Input.debugDiscardExact(n: Number, name: String = "") {
fun Input.debugDiscardExact(n: Number, name: String = "") {
DebugLogger.debug("Discarded($n) $name=" + this.readBytes(n.toInt()).toUHexString())
}
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this"))
internal fun ByteReadPacket.debugPrint(name: String = ""): ByteReadPacket {
val bytes = this.readBytes()
DebugLogger.debug("ByteReadPacket $name=" + bytes.toUHexString())
return bytes.toReadPacket()
}
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this"))
internal inline fun <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
fun ByteReadPacket.debugPrint(name: String = ""): ByteReadPacket {
ByteArrayPool.useInstance {
val count = this.readAvailable(it)
DebugLogger.debug("ByteReadPacket $name=" + it.toUHexString(offset = 0, length = count))
return it.toReadPacket(0, count)
}
}
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this"))
internal fun ByteReadPacket.debugColorizedPrint(name: String = "", ignoreUntilFirstConst: Boolean = false): ByteReadPacket {
val bytes = this.readBytes()
bytes.printColorizedHex(name, ignoreUntilFirstConst)
return bytes.toReadPacket()
}
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith(" "))
internal fun BytePacketBuilder.debugColorizedPrintThis(name: String = "") {
val data = this.build().readBytes()
data.printColorizedHex(name)
this.writeFully(data)
}
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith(" "))
internal fun BytePacketBuilder.debugColorizedPrintThis(name: String = "", compareTo: String? = null) {
val data = this.build().readBytes()
data.printColorizedHex(name, compareTo = compareTo)
this.writeFully(data)
}
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith(" "))
internal fun BytePacketBuilder.debugPrintThis(name: String = "") {
val data = this.build().readBytes()
data.printColorizedHex(name)
this.writeFully(data)
}
internal fun String.printStringFromHex() {
println(this.hexToBytes().encodeToString())
}
@UseExperimental(MiraiInternalAPI::class)
internal fun ByteArray.printColorizedHex(name: String = "", ignoreUntilFirstConst: Boolean = false, compareTo: String? = null) {
println("Hex比较 `$name`")
if (compareTo != null) {
println(printCompareHex(toUHexString(), compareTo))
} else {
println(toUHexString().printColorize(ignoreUntilFirstConst))
inline fun <R> ByteReadPacket.debugPrintIfFail(name: String = "", block: ByteReadPacket.() -> R): R {
ByteArrayPool.useInstance {
val count = this.readAvailable(it)
try {
return block(it.toReadPacket(0, count))
} catch (e: Throwable) {
DebugLogger.debug("Error in ByteReadPacket $name=" + it.toUHexString(offset = 0, length = count))
throw e
}
}
println()
}
\ No newline at end of file
......@@ -2,10 +2,13 @@
package net.mamoe.mirai.utils.io
import kotlinx.io.InputStream
import kotlinx.io.OutputStream
import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.contact.GroupId
import net.mamoe.mirai.contact.GroupInternalId
import net.mamoe.mirai.contact.groupId
import net.mamoe.mirai.contact.groupInternalId
import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
......@@ -23,6 +26,14 @@ inline fun Input.discardExact(n: UByte) = this.discardExact(n.toInt())
@Suppress("NOTHING_TO_INLINE")
inline fun Input.discardExact(n: Byte) = this.discardExact(n.toInt())
fun ByteReadPacket.transferTo(outputStream: OutputStream) {
ByteArrayPool.useInstance {
while (this.isNotEmpty) {
outputStream.write(it, 0, this.readAvailable(it))
}
}
}
fun ByteReadPacket.readRemainingBytes(
n: Int = remaining.toInt()//not that safe but adequate
): ByteArray = ByteArray(n).also { readAvailable(it, 0, n) }
......@@ -43,6 +54,11 @@ fun Input.readIP(): String = buildString(4 + 3) {
fun Input.readPacket(length: Int): ByteReadPacket = this.readBytes(length).toReadPacket()
fun Input.readQQ(): Long = this.readUInt().toLong()
fun Input.readGroup(): Long = this.readUInt().toLong()
fun Input.readGroupId(): GroupId = this.readUInt().toLong().groupId()
fun Input.readGroupInternalId(): GroupInternalId = this.readUInt().toLong().groupInternalId()
fun Input.readUVarIntLVString(): String = String(this.readUVarIntByteArray())
fun Input.readUByteLVString(): String = String(this.readUByteLVByteArray())
......@@ -55,7 +71,7 @@ fun Input.readUByteLVByteArray(): ByteArray = this.readBytes(this.readUByte().to
fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt())
internal inline fun <R> inline(block: () -> R): R = block()
private inline fun <R> inline(block: () -> R): R = block()
@Suppress("DuplicatedCode")
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) =
internal inline fun unsupported(message: String? = null): Nothing = error(message ?: "Unsupported")
internal inline fun ByteReadPacket.unsupportedFlag(name: String, flag: String): Nothing = error("Unsupported flag of $name. flag=$flag, remaining=${readBytes().toUHexString()}")
internal inline fun ByteReadPacket.unsupportedType(name: String, type: String): Nothing = error("Unsupported type of $name. type=$type, remaining=${readBytes().toUHexString()}")
internal inline fun illegalArgument(message: String? = null): Nothing = error(message ?: "Illegal argument passed")
@JvmName("printTLVStringMap")
......
......@@ -8,54 +8,52 @@ import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.contact.GroupId
import net.mamoe.mirai.contact.GroupInternalId
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
import net.mamoe.mirai.network.protocol.timpc.packet.DecrypterByteArray
import net.mamoe.mirai.network.protocol.timpc.packet.login.PrivateKey
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.Tested
import net.mamoe.mirai.utils.currentTime
import net.mamoe.mirai.utils.deviceName
import net.mamoe.mirai.utils.encryptBy
import net.mamoe.mirai.utils.internal.coerceAtMostOrFail
import kotlin.random.Random
import kotlin.random.nextInt
internal fun BytePacketBuilder.writeZero(count: Int) {
fun BytePacketBuilder.writeZero(count: Int) {
require(count != 0) { "Trying to write zero with count 0, you made a mistake?" }
require(count > 0) { "writeZero: count must > 0" }
repeat(count) { this.writeByte(0) }
}
internal fun BytePacketBuilder.writeRandom(length: Int) = repeat(length) { this.writeByte(Random.Default.nextInt(255).toByte()) }
fun BytePacketBuilder.writeRandom(length: Int) = repeat(length) { this.writeByte(Random.Default.nextInt(255).toByte()) }
internal fun BytePacketBuilder.writeQQ(qq: Long) = this.writeUInt(qq.toUInt())
internal fun BytePacketBuilder.writeQQ(qq: UInt) = this.writeUInt(qq)
internal fun BytePacketBuilder.writeGroup(groupId: GroupId) = this.writeUInt(groupId.value)
internal fun BytePacketBuilder.writeGroup(groupInternalId: GroupInternalId) = this.writeUInt(groupInternalId.value)
internal fun BytePacketBuilder.writeFully(value: DecrypterByteArray) = this.writeFully(value.value)
fun BytePacketBuilder.writeQQ(qq: Long) = this.writeUInt(qq.toUInt()) // same bit rep.
fun BytePacketBuilder.writeGroup(groupId: GroupId) = this.writeUInt(groupId.value.toUInt())
fun BytePacketBuilder.writeGroup(groupInternalId: GroupInternalId) = this.writeUInt(groupInternalId.value.toUInt())
internal fun BytePacketBuilder.writeShortLVByteArray(byteArray: ByteArray) {
fun BytePacketBuilder.writeShortLVByteArray(byteArray: ByteArray) {
this.writeShort(byteArray.size.toShort())
this.writeFully(byteArray)
}
internal fun BytePacketBuilder.writeShortLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) =
fun BytePacketBuilder.writeShortLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) =
BytePacketBuilder().apply(builder).build().use {
if (tag != null) writeUByte(tag)
writeUShort((lengthOffset?.invoke(it.remaining) ?: it.remaining).coerceAtMostOrFail(0xFFFFL).toUShort())
writePacket(it)
}
internal fun BytePacketBuilder.writeUVarIntLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) =
fun BytePacketBuilder.writeUVarIntLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) =
BytePacketBuilder().apply(builder).build().use {
if (tag != null) writeUByte(tag)
writeUVarInt((lengthOffset?.invoke(it.remaining) ?: it.remaining).coerceAtMostOrFail(0xFFFFL))
writePacket(it)
}
internal fun BytePacketBuilder.writeShortLVString(str: String) = writeShortLVByteArray(str.toByteArray())
fun BytePacketBuilder.writeShortLVString(str: String) = writeShortLVByteArray(str.toByteArray())
internal fun BytePacketBuilder.writeIP(ip: String) = writeFully(ip.trim().split(".").map { it.toUByte() }.toUByteArray())
fun BytePacketBuilder.writeIP(ip: String) = writeFully(ip.trim().split(".").map { it.toUByte() }.toUByteArray())
internal fun BytePacketBuilder.writeTime() = this.writeInt(currentTime.toInt())
fun BytePacketBuilder.writeTime() = this.writeInt(currentTime.toInt())
internal fun BytePacketBuilder.writeHex(uHex: String) {
fun BytePacketBuilder.writeHex(uHex: String) {
uHex.split(" ").forEach {
if (it.isNotBlank()) {
writeUByte(it.toUByte(16))
......@@ -63,49 +61,49 @@ internal fun BytePacketBuilder.writeHex(uHex: String) {
}
}
internal fun <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)
writeUVarInt(values.size.toUInt())
writeFully(values)
}
internal fun BytePacketBuilder.writeTLV(tag: UByte, values: ByteArray) {
fun BytePacketBuilder.writeTLV(tag: UByte, values: ByteArray) {
writeUByte(tag)
writeUVarInt(values.size.toUInt())
writeFully(values)
}
internal fun BytePacketBuilder.writeTHex(tag: UByte, uHex: String) {
fun BytePacketBuilder.writeTHex(tag: UByte, uHex: String) {
this.writeUByte(tag)
this.writeFully(uHex.hexToUBytes())
}
internal fun BytePacketBuilder.writeTV(tagValue: UShort) = writeUShort(tagValue)
fun BytePacketBuilder.writeTV(tagValue: UShort) = writeUShort(tagValue)
internal fun BytePacketBuilder.writeTV(tag: UByte, value: UByte) {
fun BytePacketBuilder.writeTV(tag: UByte, value: UByte) {
writeUByte(tag)
writeUByte(value)
}
internal fun BytePacketBuilder.writeTUbyte(tag: UByte, value: UByte) {
fun BytePacketBuilder.writeTUbyte(tag: UByte, value: UByte) {
this.writeUByte(tag)
this.writeUByte(value)
}
internal fun BytePacketBuilder.writeTUVarint(tag: UByte, value: UInt) {
fun BytePacketBuilder.writeTUVarint(tag: UByte, value: UInt) {
this.writeUByte(tag)
this.writeUVarInt(value)
}
internal fun BytePacketBuilder.writeTByteArray(tag: UByte, value: ByteArray) {
fun BytePacketBuilder.writeTByteArray(tag: UByte, value: ByteArray) {
this.writeUByte(tag)
this.writeFully(value)
}
internal fun BytePacketBuilder.writeTByteArray(tag: UByte, value: UByteArray) {
fun BytePacketBuilder.writeTByteArray(tag: UByte, value: UByteArray) {
this.writeUByte(tag)
this.writeFully(value)
}
......@@ -113,43 +111,18 @@ internal fun BytePacketBuilder.writeTByteArray(tag: UByte, value: UByteArray) {
/**
* 会使用 [ByteArrayPool] 缓存
*/
internal inline fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) =
inline fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) =
BytePacketBuilder().apply(encoder).build().encryptBy(key) { decrypted -> writeFully(decrypted) }
internal inline fun BytePacketBuilder.encryptAndWrite(key: IoBuffer, encoder: BytePacketBuilder.() -> Unit) = ByteArrayPool.useInstance {
inline fun BytePacketBuilder.encryptAndWrite(key: IoBuffer, encoder: BytePacketBuilder.() -> Unit) = ByteArrayPool.useInstance {
key.readFully(it, 0, key.readRemaining)
encryptAndWrite(it, encoder)
}
internal inline fun BytePacketBuilder.encryptAndWrite(key: DecrypterByteArray, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(key.value, encoder)
internal inline fun BytePacketBuilder.encryptAndWrite(keyHex: String, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(keyHex.hexToBytes(), encoder)
internal fun BytePacketBuilder.writeTLV0006(qq: UInt, password: String, loginTime: Int, loginIP: String, privateKey: PrivateKey) {
val firstMD5 = md5(password)
val secondMD5 = md5(firstMD5 + byteArrayOf(0, 0, 0, 0) + qq.toUInt().toByteArray())
this.encryptAndWrite(secondMD5) {
writeRandom(4)
writeHex("00 02")
writeQQ(qq)
writeFully(TIMProtocol.constantData2)
writeHex("00 00 01")
writeFully(firstMD5)
writeInt(loginTime)
writeByte(0)
writeZero(4 * 3)
writeIP(loginIP)
writeZero(8)
writeHex("00 10")//这两个hex是passwordSubmissionTLV2的末尾
writeHex("15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B")//16
writeFully(privateKey.value)
}
}
inline fun BytePacketBuilder.encryptAndWrite(keyHex: String, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(keyHex.hexToBytes(), encoder)
@Tested
internal fun BytePacketBuilder.writeDeviceName(random: Boolean) {
fun BytePacketBuilder.writeDeviceName(random: Boolean) {
val deviceName: String = if (random) {
"DESKTOP-" + String(ByteArray(7) {
(if (Random.nextBoolean()) Random.nextInt('A'.toInt()..'Z'.toInt())
......
......@@ -23,9 +23,9 @@ expect class ClosedChannelException : IOException
/**
* 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误.
*/
internal class SendPacketInternalException(cause: Throwable?) : Exception(cause)
class SendPacketInternalException(cause: Throwable?) : Exception(cause)
/**
* 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误.
*/
internal class ReadPacketInternalException(cause: Throwable?) : Exception(cause)
\ No newline at end of file
class ReadPacketInternalException(cause: Throwable?) : Exception(cause)
\ No newline at end of file
......@@ -3,7 +3,6 @@
package net.mamoe.mirai.utils.io
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.writeFully
import kotlinx.io.pool.ObjectPool
import kotlin.random.Random
import kotlin.random.nextInt
......@@ -105,8 +104,7 @@ fun String.hexToUBytes(): UByteArray =
/**
* 生成长度为 [length], 元素为随机 `0..255` 的 [ByteArray]
*/
@PublishedApi
internal fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() }
fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() }
/**
* 随机生成长度为 [length] 的 [String].
......@@ -142,4 +140,4 @@ fun ByteArray.toUShort(): UShort =
* 从 [IoBuffer.Pool] [borrow][ObjectPool.borrow] 一个 [IoBuffer] 然后将 [this] 写入.
* 注意回收 ([ObjectPool.recycle])
*/
fun ByteArray.toIoBuffer(): IoBuffer = IoBuffer.Pool.borrow().let { it.writeFully(this); it }
\ No newline at end of file
fun ByteArray.toIoBuffer(offset: Int = 0, length: Int = this.size - offset): IoBuffer = IoBuffer.Pool.borrow().let { it.writeFully(this, offset, length); it }
\ No newline at end of file
......@@ -2,7 +2,6 @@ package net.mamoe.mirai.utils.io
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.asserter
class TypeConversionTest {
......
@file:Suppress("unused")
package net.mamoe.mirai.network.protocol.timpc.packet.event
package net.mamoe.mirai.message
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.io.core.Input
import kotlinx.io.core.use
import kotlinx.io.streams.inputStream
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.*
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.toExternalImage
......@@ -24,7 +25,7 @@ import javax.imageio.ImageIO
* JVM 平台相关扩展
*/
@UseExperimental(MiraiInternalAPI::class)
actual abstract class MessagePacket<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: URL): Image = subject.uploadImage(image)
suspend inline fun uploadImage(image: Input): Image = subject.uploadImage(image)
......
......@@ -6,7 +6,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.io.core.Input
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.network.protocol.timpc.packet.action.OverFileSizeMaxException
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.utils.OverFileSizeMaxException
import net.mamoe.mirai.utils.sendTo
import net.mamoe.mirai.utils.toExternalImage
import net.mamoe.mirai.utils.upload
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused")
package net.mamoe.mirai.utils
import io.ktor.util.asStream
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.io.jvm.javaio.copyTo
import kotlinx.coroutines.withContext
import kotlinx.io.core.Input
import kotlinx.io.core.IoBuffer
......@@ -48,8 +47,7 @@ fun BufferedImage.toExternalImage(formatName: String = "gif"): ExternalImage {
return ExternalImage(width, height, digest.digest(), formatName, buffer)
}
@Suppress("unused")
suspend fun BufferedImage.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
suspend inline fun BufferedImage.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
/**
* 读取文件头识别图片属性, 然后构造 [ExternalImage]
......@@ -75,8 +73,7 @@ fun File.toExternalImage(): ExternalImage {
/**
* 在 [IO] 中进行 [File.toExternalImage]
*/
@Suppress("unused")
suspend fun File.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
suspend inline fun File.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
/**
* 下载文件到临时目录然后调用 [File.toExternalImage]
......@@ -95,8 +92,7 @@ fun URL.toExternalImage(): ExternalImage {
/**
* 在 [IO] 中进行 [URL.toExternalImage]
*/
@Suppress("unused")
suspend fun URL.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
suspend inline fun URL.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
/**
* 保存为临时文件然后调用 [File.toExternalImage]
......@@ -114,11 +110,12 @@ fun InputStream.toExternalImage(): ExternalImage {
/**
* 在 [IO] 中进行 [InputStream.toExternalImage]
*/
@Suppress("unused")
suspend fun InputStream.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
suspend inline fun InputStream.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
/**
* 保存为临时文件然后调用 [File.toExternalImage]
* 保存为临时文件然后调用 [File.toExternalImage].
*
* 需要函数调用者 close [this]
*/
@Throws(IOException::class)
fun Input.toExternalImage(): ExternalImage {
......@@ -132,5 +129,4 @@ fun Input.toExternalImage(): ExternalImage {
/**
* 在 [IO] 中进行 [Input.toExternalImage]
*/
@Suppress("unused")
suspend fun Input.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
suspend inline fun Input.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
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
import net.mamoe.mirai.test.shouldBeEqualTo
import org.junit.Test
import kotlin.random.Random
@UseExperimental(ExperimentalUnsignedTypes::class)
internal class GroupIdConversionsKtTest {
@Test
fun checkToInternalId() {
GroupId(221056495u).toInternalId().value shouldBeEqualTo 4111056495u
GroupId(221056495).toInternalId().value shouldBeEqualTo 4111056495
// 61 056495
//4111 056495
}
......
......@@ -35,12 +35,14 @@ kotlin {
}
}
fun DependencyHandlerScope.kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version"
fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version"
fun DependencyHandlerScope.ktor(id: String, version: String) = "io.ktor:ktor-$id:$version"
fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version"
dependencies {
implementation(project(":mirai-core"))
implementation(files("../mirai-core-timpc/build/classes/kotlin/jvm/main")) // IDE bug
implementation(project(":mirai-core-timpc"))
// runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // classpath is not added correctly by IDE
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
......
@file:Suppress("ObjectPropertyName", "unused", "NonAsciiCharacters", "MayBeConstant")
@file:Suppress("ObjectPropertyName", "MayBeConstant", "NonAsciiCharacters", "SpellCheckingInspection", "unused", "EXPERIMENTAL_UNSIGNED_LITERALS")
package hex
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.internal.printCompareHex
@UseExperimental(MiraiInternalAPI::class)
fun main() {
// println(HexComparator.printColorize("00 00 00 25 00 08 00 02 00 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 01 38 03 3E 03 3F A2 76 E4 B8 DD E7 86 74 F2 64 55 AD 9A EB 2F B9 DF F1 7F 8C 28 00 0B 78 14 5D A2 F5 CB 01 1D 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00 5D A2 F5 CA 9D 26 CB 5E 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E4 BD A0 E5 A5 BD 0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00"))
while (true) {
println("Hex1: ")
val hex1 = readLine()!!
println("Hex2: ")
val hex2 = readLine()!!
println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
println(printCompareHex(hex1.toUpperCase(), hex2.toUpperCase()))
println()
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))
}
}
}
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(
//mirai
"2A 22 96 29 7B 00 40 00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 EC 21 40 06 18 89 54 BC Protocol.messageConst1 00 00 01 00 0A 01 00 07 E7 89 9B E9 80 BC 21\n"
,
//e
"2A 22 96 29 7B 00 3F 00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 5D 6B 8E 1A FE 39 0B FC Protocol.messageConst1 00 00 01 00 0A 01 00 07 6D 65 73 73 61 67 65"
));
*/
/*
System.out.println(HexComparator.compare(
//e
"90 5E 39 DF 00 02 76 E4 B8 DD 00 00 04 53 00 00 00 01 00 00 15 85 00 00 01 55 35 05 8E C9 BA 16 D0 01 63 5B 59 4B 59 52 31 01 B9 00 00 00 00 00 00 00 00 00 00 00 00 00 7B 7B 7B 7B 00 00 00 00 00 00 00 00 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B AA BB CC DD EE FF AA BB CC",
//mirai
"6F 0B DF 92 00 02 76 E4 B8 DD 00 00 04 53 00 00 00 01 00 00 15 85 00 00 01 55 35 05 8E C9 BA 16 D0 01 63 5B 59 4B 59 52 31 01 B9 00 00 00 00 00 00 00 00 00 00 00 00 00 E9 E9 E9 E9 00 00 00 00 00 00 00 00 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B AA BB CC DD EE FF AA BB CC\n\n\n"
));*/
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
package test
import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult
import net.mamoe.mirai.network.data.LoginResult
@Suppress("RedundantSuspendModifier")
suspend fun suspendPrintln(arg: String) = println(arg)
......
......@@ -9,7 +9,6 @@ import net.mamoe.mirai.utils.io.toUHexString
@ExperimentalStdlibApi
@Suppress("EXPERIMENTAL_API_USAGE")
fun main() {
val newMap =
"4E 22 00 03 E5 AE 89 4E 25 00 06 35 31 31 34 39 35 4E 26 00 01 2D 4E 27 00 01 2D 4E 29 00 01 02 4E 2A 00 06 56 69 76 69 61 6E 4E 2B 00 10 31 35 36 31 34 38 39 31 33 40 71 71 2E 63 6F 6D 4E 2D 00 1D 68 74 74 70 3A 2F 2F 31 35 36 31 34 38 39 31 33 2E 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 4E 2E 00 02 33 00 4E 2F 00 04 33 33 39 00 4E 30 00 01 2D 4E 31 00 01 00 4E 33 00 2D E6 88 91 E7 95 99 E9 95 BF E7 9A 84 E5 A4 B4 E5 8F 91 EF BC 8C E6 98 AF E4 BD A0 E9 94 99 E8 BF 87 E7 9A 84 E5 B9 B4 E5 8D 8E 2E 2E 2E 4E 35 00 18 E5 B9 BF E4 B8 9C E6 8A 80 E6 9C AF E5 B8 88 E8 8C 83 E5 AD A6 E9 99 A2 4E 36 00 01 0A 4E 37 00 01 03 4E 38 00 01 01 4E 3F 00 04 07 C2 0B 02 4E 40 00 0C 00 00 00 31 00 00 34 34 00 00 00 33 4E 41 00 02 00 00 4E 42 00 02 00 00 4E 43 00 02 00 00 4E 45 00 01 21 4E 49 00 04 00 00 00 00 4E 4B 00 04 00 00 00 00 4E 4F 00 01 00 4E 54 00 00 4E 5B 00 00 52 0B 00 04 13 88 02 02 52 0F 00 14 00 00 00 00 00 00 00 00 12 05 10 58 89 10 00 00 00 00 00 00 5D C2 00 0C 00 00 00 31 00 00 34 34 00 00 31 34 5D C8 00 1E E7 B4 A2 E5 B0 BC EF BC 88 E4 B8 AD E5 9B BD EF BC 89 E6 9C 89 E9 99 90 E5 85 AC E5 8F B8 65 97 00 01 11 69 9D 00 04 00 00 00 00 69 A9 00 00 9D A5 00 02 00 00 A4 91 00 02 00 00 A4 93 00 02 00 00 A4 94 00 02 00 00 A4 9C 00 02 00 00 A4 B5 00 02 00 00"
.hexToBytes().read {
......
......@@ -2,8 +2,10 @@ apply plugin: "kotlin"
apply plugin: "java"
dependencies {
api project(":mirai-core")
// runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // classpath is not set correctly by IDE
implementation files("../../mirai-core-timpc/build/classes/kotlin/jvm/main") // IDE bug
implementation project(":mirai-core-timpc")
api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion
}
......@@ -10,10 +10,14 @@ import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.event.*
import net.mamoe.mirai.message.*
import net.mamoe.mirai.network.protocol.timpc.packet.action.uploadImage
import net.mamoe.mirai.network.protocol.timpc.packet.event.FriendMessage
import net.mamoe.mirai.network.protocol.timpc.packet.event.GroupMessage
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.ImageId
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.firstOrNull
import net.mamoe.mirai.message.sendAsImageTo
import net.mamoe.mirai.timpc.TIMPC
import net.mamoe.mirai.utils.suspendToExternalImage
import java.io.File
......@@ -25,7 +29,7 @@ private fun readTestAccount(): BotAccount? {
val lines = file.readLines()
return try {
BotAccount(lines[0].toUInt(), lines[1])
BotAccount(lines[0].toLong(), lines[1])
} catch (e: IndexOutOfBoundsException) {
null
}
......@@ -33,9 +37,9 @@ private fun readTestAccount(): BotAccount? {
@Suppress("UNUSED_VARIABLE")
suspend fun main() {
val bot = Bot(
val bot = TIMPC.Bot(
readTestAccount() ?: BotAccount(//填写你的账号
id = 1994701121u,
id = 1994701121,
password = "123456"
)
).alsoLogin {
......@@ -211,37 +215,65 @@ suspend fun directlySubscribe(bot: Bot) {
"复读" in it.message -> it.sender.sendMessage(it.message)
"发群消息" in it.message -> 580266363u.group().sendMessage(it.message.toString().substringAfter("发群消息"))
"发群消息" in it.message -> 580266363.group().sendMessage(it.message.toString().substringAfter("发群消息"))
"上传群图片" in it.message -> withTimeoutOrNull(5000) {
val filename = it.message.toString().substringAfter("上传群图片")
val image = File(
"C:\\Users\\Him18\\Desktop\\$filename"
).suspendToExternalImage()
920503456u.group().uploadImage(image)
920503456.group().uploadImage(image)
it.reply(image.groupImageId.value)
delay(100)
920503456u.group().sendMessage(Image(image.groupImageId))
920503456.group().sendMessage(Image(image.groupImageId))
}
"发群图片" in it.message -> {
920503456u.group().sendMessage(Image(ImageId(it.message.toString().substringAfter("发群图片"))))
920503456.group().sendMessage(
Image(
ImageId(
it.message.toString().substringAfter(
"发群图片"
)
)
)
)
}
"发好友图片" in it.message -> {
it.reply(Image(ImageId(it.message.toString().substringAfter("发好友图片"))))
it.reply(
Image(
ImageId(
it.message.toString().substringAfter(
"发好友图片"
)
)
)
)
}
/*it.event eq "发图片群" -> sendGroupMessage(Group(session.bot, 580266363), PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image ->
image.upload(session, Group(session.bot, 580266363)).of()
})*/
it.message eq "发图片群2" -> 580266363u.group().sendMessage(Image(ImageId("{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg")))
it.message eq "发图片群2" -> 580266363.group().sendMessage(
Image(
ImageId(
"{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg"
)
)
)
/* it.event eq "发图片" -> sendFriendMessage(it.sentBy, PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image ->
image.upload(session, it.sentBy).of()
})*/
it.message eq "发图片2" -> it.reply(PlainText("test") + Image(ImageId("{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg")))
it.message eq "发图片2" -> it.reply(
PlainText("test") + Image(
ImageId(
"{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg"
)
)
)
else -> {
}
......
......@@ -40,7 +40,7 @@ kotlin {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib"
implementation project(':mirai-core')
implementation project(':mirai-core-timpc')
implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion
implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion
......
......@@ -95,7 +95,7 @@ class MainActivity : AppCompatActivity(),LoginCallback {
}
binder?.setCallback(this)
if (!needCaptcha){
val qq = et_qq.text.toString().toUInt()
val qq = et_qq.text.toString().toLong()
val pwd = et_pwd.text.toString()
binder?.startLogin(qq, pwd)
}else{
......
......@@ -12,13 +12,9 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.io.core.readBytes
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.login
import net.mamoe.mirai.message.Image
import net.mamoe.mirai.network.protocol.timpc.packet.event.GroupMessage
import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult
import net.mamoe.mirai.network.data.LoginResult
import net.mamoe.mirai.timpc.TIMPC
import java.lang.ref.WeakReference
class MiraiService : Service() {
......@@ -43,9 +39,9 @@ class MiraiService : Service() {
}
private fun login(qq: UInt, pwd: String) {
private fun login(qq: Long, pwd: String) {
GlobalScope.launch {
mBot = Bot(qq, pwd).apply {
mBot = TIMPC.Bot(qq, pwd).apply {
val loginResult = login {
captchaSolver = {
val bytes = it.readBytes()
......@@ -64,75 +60,12 @@ class MiraiService : Service() {
mBot.subscribeMessages {
content({ true }) {
always {
mCallback?.get()?.onMessage("收到来自${sender.id}的消息")
}
// 当接收到消息 == "你好" 时就回复 "你好!"
"你好" reply "你好!"
// 当消息 == "查看 subject" 时, 执行 lambda
case("查看 subject") {
if (subject is QQ) {
reply("消息主体为 QQ, 你在跟发私聊消息")
} else {
reply("消息主体为 Group, 你在群里发消息")
}
// 在回复的时候, 一般使用 subject 来作为回复对象.
// 因为当群消息时, subject 为这个群.
// 当好友消息时, subject 为这个好友.
// 所有在 MessagePacket(也就是此时的 this 指代的对象) 中实现的扩展方法, 如刚刚的 "reply", 都是以 subject 作为目标
}
// 当消息里面包含这个类型的消息时
has<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() {
inner class MiraiBinder : Binder() {
fun startLogin(qq: UInt, pwd: String) {
fun startLogin(qq: Long, pwd: String) {
login(qq, pwd)
}
......
......@@ -3,8 +3,8 @@ apply plugin: "java"
apply plugin: "application"
dependencies {
api project(":mirai-core")
//runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // classpath is not set correctly by IDE
implementation files("../../mirai-core-timpc/build/classes/kotlin/jvm/main") // IDE bug
implementation project(":mirai-core-timpc")
implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion
implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion
......
......@@ -3,7 +3,7 @@ package demo.gentleman
import com.alibaba.fastjson.JSON
import kotlinx.coroutines.*
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.message.Image
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.uploadAsImage
import org.jsoup.Jsoup
......
......@@ -17,7 +17,7 @@ private const val IMAGE_BUFFER_CAPACITY: Int = 5
*/
@ExperimentalUnsignedTypes
@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) }
}
......
......@@ -7,16 +7,22 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.mamoe.mirai.*
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.event.events.ReceiveFriendAddRequestEvent
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.event.subscribeGroupMessages
import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.message.*
import net.mamoe.mirai.network.protocol.timpc.packet.event.FriendMessage
import net.mamoe.mirai.network.protocol.timpc.packet.event.GroupMessage
import net.mamoe.mirai.network.protocol.timpc.packet.event.ReceiveFriendAddRequestEvent
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.data.At
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.buildXMLMessage
import net.mamoe.mirai.message.data.getValue
import net.mamoe.mirai.message.sendAsImageTo
import net.mamoe.mirai.timpc.TIMPC
import java.io.File
import java.util.*
import javax.swing.filechooser.FileSystemView
......@@ -30,7 +36,7 @@ private fun readTestAccount(): BotAccount? {
val lines = file.readLines()
return try {
BotAccount(lines[0].toUInt(), lines[1])
BotAccount(lines[0].toLong(), lines[1])
} catch (e: Exception) {
null
}
......@@ -38,9 +44,9 @@ private fun readTestAccount(): BotAccount? {
@Suppress("UNUSED_VARIABLE")
suspend fun main() {
val bot = Bot(
val bot = TIMPC.Bot(
readTestAccount() ?: BotAccount(
id = 913366033u,
id = 913366033,
password = "a18260132383"
)
).alsoLogin()
......@@ -86,7 +92,7 @@ suspend fun main() {
startsWith("profile", removePrefix = true) {
val account = it.trim()
if (account.isNotEmpty()) {
account.toUInt().qq()
account.toLong().qq()
} else {
sender
}.queryProfile().toString().reply()
......@@ -168,7 +174,7 @@ suspend fun main() {
}
startsWith("添加好友", removePrefix = true) {
reply(bot.addFriend(it.toUInt()).toString())
reply(bot.addFriend(it.toLong()).toString())
}
}
......
......@@ -22,6 +22,7 @@ pluginManagement {
rootProject.name = 'mirai'
include(':mirai-core')
include(':mirai-core-timpc')
include(':mirai-console')
//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