Commit b2542cd6 authored by jiahua.liu's avatar jiahua.liu

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/MsgSvc.kt
#	mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt
#	mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt
#	mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/clientToServer.kt
parents 01c478e4 a79032e8
...@@ -5,27 +5,30 @@ ...@@ -5,27 +5,30 @@
[![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/) [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/)
**[English](README-eng.md)** **[English](README-eng.md)**
**TIM PC 和 QQ Android 协议** 跨平台 QQ 协议支持库. 跨平台 **TIM PC 和 QQ Android** 协议支持库.
**纯 Kotlin 实现协议和支持框架. 目前可运行在 JVM 或 Android.** 纯 Kotlin 实现协议和支持框架,模块全部开源。
目前可运行在 JVM 或 Android。
**一切开发旨在学习,请勿用于非法用途** **一切开发旨在学习,请勿用于非法用途**
您可在 Gitter 提问, 或加入 QQ 群: 655057127 加入 Gitter, 或加入 QQ 群: 655057127
## Update log ## Update log
[Project](https://github.com/mamoe/mirai/projects/1) 查看已支持功能和计划 [Project](https://github.com/mamoe/mirai/projects/1) 查看已支持功能和计划(更新不及时)
[UpdateLog](https://github.com/mamoe/mirai/blob/master/UpdateLog.md) 查看版本更新记录 [UpdateLog](https://github.com/mamoe/mirai/blob/master/UpdateLog.md) 查看版本更新记录(准确更新发布的版本)
## Features ## Modules
#### mirai-core #### mirai-core
通用 API 模块,请参考此模块调用 Mirai. 通用 API 模块,一套 API 适配两套协议。
**请参考此模块的 API**
#### mirai-core-timpc #### mirai-core-timpc
TIM PC (2.3.2 版本,2019 年 8 月)协议的实现,相较于 core,仅新增少量 API. 详见 [README.md](mirai-core-timpc/) TIM PC (2.3.2 版本,2019 年 8 月)协议的实现,相较于 core,仅新增少量 API. 详见 [README.md](mirai-core-timpc/)
支持的功能: 支持的功能:
- 消息收发:图片文字复合消息,图片消息 - 消息收发:图片文字复合消息,图片消息
- 群管功能:群员列表,禁言 - 群管功能:群员列表,禁言
(目前不再更新,请关注安卓协议) (目前不再更新此协议,请关注下文的安卓协议)
#### mirai-core-qqandroid #### mirai-core-qqandroid
QQ for Android (8.2.0 版本,2019 年 12 月)协议的实现,目前还未完成。 QQ for Android (8.2.0 版本,2019 年 12 月)协议的实现,目前还未完成。
...@@ -42,25 +45,31 @@ QQ for Android (8.2.0 版本,2019 年 12 月)协议的实现,目前还 ...@@ -42,25 +45,31 @@ QQ for Android (8.2.0 版本,2019 年 12 月)协议的实现,目前还
- 进行中 图片上传和下载 - 进行中 图片上传和下载
## Use directly ## Use directly
**直接使用Mirai(终端环境/网页面板(将来)).** **直接使用 Mirai(终端环境/网页面板(将来)).**
[Mirai-Console](https://github.com/mamoe/mirai/tree/master/mirai-console) 插件支持, 在终端中启动Mirai并获得机器人服务 [Mirai-Console](https://github.com/mamoe/mirai/tree/master/mirai-console) 插件支持, 在终端中启动 Mirai 并获得机器人服务
本模块还未完善。
## Use as a library ## Use as a library
**mirai-core 为独立设计, 可以作为库内置于您的任意 Java/Android 项目中使用.** **mirai-core 为独立设计, 可以作为库内置于任意 Java(JVM)/Android 项目中使用.**
Mirai 只上传在 `jcenter`, 因此请确保在 `build.gradle` 添加 `jcenter()` 仓库
### Gradle
Mirai 只发布在 `jcenter`, 因此请确保在 `build.gradle` 添加 `jcenter()` 仓库:
```kotlin ```kotlin
repositories{ repositories{
jcenter() jcenter()
} }
``` ```
若您需要使用在跨平台项目, 您需要对各个目标平台添加不同的依赖. 若您需要使用在跨平台项目, 则要对各个目标平台添加不同的依赖,这与 kotlin 相关跨平台库的依赖是类似的。
**若您只需要使用在单一平台, 则只需要添加一项该平台的依赖. 如只在JVM运行则只需要`-jvm`的依赖** **若您只需要使用在单一平台, 则只需要添加一项该平台的依赖. 如只在 JVM 运行则只需要`-jvm`的依赖**
您需要`VERSION` 替换为最新的版本(如 `0.10.6`): `VERSION` 替换为最新的版本(如 `0.10.6`):
[![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/) [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/)
**Mirai 目前还处于实验性阶段, 我们无法保证任何稳定性, API 也可能会随时修改.** **Mirai 目前还处于实验性阶段, 我们无法保证任何稳定性, API 也可能会随时修改.**
现在 Mirai 只支持 TIM PC 协议. QQ Android 协议正在开发中. **注意:**
Mirai 核心由 API 模块(`mirai-core`)和协议模块组成。
只添加 API 模块将无法正常工作。
现在只推荐使用 TIMPC 协议,请参照下文选择对应目标平台的依赖添加。
**common** **common**
```kotlin ```kotlin
...@@ -90,7 +99,7 @@ JVM 上需 120M-150M 内存 ...@@ -90,7 +99,7 @@ JVM 上需 120M-150M 内存
您的 star 是对我们最大的鼓励(点击项目右上角); 您的 star 是对我们最大的鼓励(点击项目右上角);
## Wiki ## Wiki
[Wiki](https://github.com/mamoe/mirai/wiki/Development-Guide---Kotlin) 中查看各类帮助,如 API 示例 [Wiki](https://github.com/mamoe/mirai/wiki/Development-Guide---Kotlin) 中查看各类帮助,**如 API 示例**(可能过时,待 QQ Android 协议完成后会重写)
## Try ## Try
...@@ -110,6 +119,8 @@ bot.subscribeAlways<MemberPermissionChangedEvent> { ...@@ -110,6 +119,8 @@ bot.subscribeAlways<MemberPermissionChangedEvent> {
} }
``` ```
我们也考虑到了 Java 兼容的问题,这正在计划中,但不是高优先的。
1. Clone 1. Clone
2. Import as Gradle project 2. Import as Gradle project
3. 运行 Demo 程序: [mirai-demo](#mirai-demo) 示例和演示程序 3. 运行 Demo 程序: [mirai-demo](#mirai-demo) 示例和演示程序
...@@ -119,6 +130,7 @@ bot.subscribeAlways<MemberPermissionChangedEvent> { ...@@ -119,6 +130,7 @@ bot.subscribeAlways<MemberPermissionChangedEvent> {
- Kotlin 1.3.61 - Kotlin 1.3.61
- JDK 8 (required) - JDK 8 (required)
- JDK 11(for protocol tools, optional)
- Android SDK 29 (for Android target, optional) - Android SDK 29 (for Android target, optional)
#### Libraries used #### Libraries used
...@@ -144,5 +156,5 @@ bot.subscribeAlways<MemberPermissionChangedEvent> { ...@@ -144,5 +156,5 @@ bot.subscribeAlways<MemberPermissionChangedEvent> {
- (见 LICENSE 第 4 节) 您可以免费或收费地传递这个项目的源代码或目标代码(即编译结果), **但前提是提供明显的版权声明** (您需要标注本 `GitHub` 项目地址) - (见 LICENSE 第 4 节) 您可以免费或收费地传递这个项目的源代码或目标代码(即编译结果), **但前提是提供明显的版权声明** (您需要标注本 `GitHub` 项目地址)
## Acknowledgement ## Acknowledgement
特别感谢 [JetBrains](https://www.jetbrains.com/?from=mirai) 提供的免费 [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=mirai) 等 IDE 授权 特别感谢 [JetBrains](https://www.jetbrains.com/?from=mirai) 为开源项目提供免费的 [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=mirai) 等 IDE 的授权
[<img src=".github/jetbrains-variant-3.png" width="200"/>](https://www.jetbrains.com/?from=mirai) [<img src=".github/jetbrains-variant-3.png" width="200"/>](https://www.jetbrains.com/?from=mirai)
...@@ -8,14 +8,15 @@ import net.mamoe.mirai.data.ImageLink ...@@ -8,14 +8,15 @@ import net.mamoe.mirai.data.ImageLink
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.ImageIdQQA
import net.mamoe.mirai.qqandroid.utils.Context import net.mamoe.mirai.qqandroid.utils.Context
import net.mamoe.mirai.qqandroid.utils.ImageIdQQA
import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.LockFreeLinkedList import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
internal expect class QQAndroidBot( @UseExperimental(MiraiInternalAPI::class)
internal expect class QQAndroidBot constructor(
context: Context, context: Context,
account: BotAccount, account: BotAccount,
configuration: BotConfiguration configuration: BotConfiguration
...@@ -28,7 +29,7 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -28,7 +29,7 @@ internal abstract class QQAndroidBotBase constructor(
configuration: BotConfiguration configuration: BotConfiguration
) : BotImpl<QQAndroidBotNetworkHandler>(account, configuration) { ) : BotImpl<QQAndroidBotNetworkHandler>(account, configuration) {
val client: QQAndroidClient = QQAndroidClient(context, account, bot = @Suppress("LeakingThis") this as QQAndroidBot) val client: QQAndroidClient = QQAndroidClient(context, account, bot = @Suppress("LeakingThis") this as QQAndroidBot)
override val uin: Long get() = client.uin
override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList()) override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList())
override fun getQQ(id: Long): QQ { override fun getQQ(id: Long): QQ {
......
package net.mamoe.mirai.qqandroid.io
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.Input
import kotlinx.io.core.readBytes
import kotlinx.io.core.writeFully
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerializationStrategy
/**
* 仅有标示作用
*/
interface ProtoBuf
fun <T : ProtoBuf> BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy<T>, v: T) {
this.writeFully(v.toByteArray(serializer))
}
/**
* dump
*/
fun <T : ProtoBuf> T.toByteArray(serializer: SerializationStrategy<T>): ByteArray {
return kotlinx.serialization.protobuf.ProtoBuf.dump(serializer, this)
}
/**
* load
*/
fun <T : ProtoBuf> ByteArray.loadAs(deserializer: DeserializationStrategy<T>): T {
return kotlinx.serialization.protobuf.ProtoBuf.load(deserializer, this)
}
/**
* load
*/
fun <T : ProtoBuf> Input.readRemainingAsProtoBuf(serializer: DeserializationStrategy<T>): T {
return kotlinx.serialization.protobuf.ProtoBuf.load(serializer, this.readBytes())
}
\ No newline at end of file
...@@ -8,7 +8,7 @@ import kotlinx.serialization.modules.EmptyModule ...@@ -8,7 +8,7 @@ import kotlinx.serialization.modules.EmptyModule
import kotlinx.serialization.modules.SerialModule import kotlinx.serialization.modules.SerialModule
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.network.protocol.packet.withUse import net.mamoe.mirai.qqandroid.network.protocol.packet.withUse
import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.utils.io.readIoBuffer import net.mamoe.mirai.utils.io.readIoBuffer
import net.mamoe.mirai.utils.io.readString import net.mamoe.mirai.utils.io.readString
import net.mamoe.mirai.utils.io.toIoBuffer import net.mamoe.mirai.utils.io.toIoBuffer
......
...@@ -5,6 +5,7 @@ import kotlinx.atomicfu.atomic ...@@ -5,6 +5,7 @@ import kotlinx.atomicfu.atomic
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.toByteArray import kotlinx.io.core.toByteArray
import net.mamoe.mirai.BotAccount import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.RawAccountIdUse
import net.mamoe.mirai.data.OnlineStatus import net.mamoe.mirai.data.OnlineStatus
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
...@@ -32,7 +33,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef ...@@ -32,7 +33,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef
DOMAINS DOMAINS
Pskey: "openmobile.qq.com" Pskey: "openmobile.qq.com"
*/ */
@UseExperimental(MiraiExperimentalAPI::class, MiraiInternalAPI::class)
@PublishedApi @PublishedApi
internal open class QQAndroidClient( internal open class QQAndroidClient(
context: Context, context: Context,
...@@ -53,7 +54,7 @@ internal open class QQAndroidClient( ...@@ -53,7 +54,7 @@ internal open class QQAndroidClient(
"tgtgtKey" to tgtgtKey, "tgtgtKey" to tgtgtKey,
"tgtKey" to wLoginSigInfo.tgtKey, "tgtKey" to wLoginSigInfo.tgtKey,
"deviceToken" to wLoginSigInfo.deviceToken, "deviceToken" to wLoginSigInfo.deviceToken,
"shareKeyCalculatedByConstPubKey" to ecdh.keyPair.shareKey "shareKeyCalculatedByConstPubKey" to ecdh.keyPair.initialShareKey
//"t108" to wLoginSigInfo.t1, //"t108" to wLoginSigInfo.t1,
//"t10c" to t10c, //"t10c" to t10c,
//"t163" to t163 //"t163" to t163
...@@ -69,7 +70,6 @@ internal open class QQAndroidClient( ...@@ -69,7 +70,6 @@ internal open class QQAndroidClient(
return null return null
} }
@UseExperimental(MiraiInternalAPI::class)
override fun toString(): String { // net.mamoe.mirai.utils.cryptor.ProtoKt.contentToString override fun toString(): String { // net.mamoe.mirai.utils.cryptor.ProtoKt.contentToString
return "QQAndroidClient(account=$account, ecdh=$ecdh, device=$device, tgtgtKey=${tgtgtKey.contentToString()}, randomKey=${randomKey.contentToString()}, miscBitMap=$miscBitMap, mainSigMap=$mainSigMap, subSigMap=$subSigMap, openAppId=$openAppId, apkVersionName=${apkVersionName.contentToString()}, loginState=$loginState, appClientVersion=$appClientVersion, networkType=$networkType, apkSignatureMd5=${apkSignatureMd5.contentToString()}, protocolVersion=$protocolVersion, apkId=${apkId.contentToString()}, t150=${t150?.contentToString()}, rollbackSig=${rollbackSig?.contentToString()}, ipFromT149=${ipFromT149?.contentToString()}, timeDifference=$timeDifference, uin=$uin, t530=${t530?.contentToString()}, t528=${t528?.contentToString()}, ksid='$ksid', pwdFlag=$pwdFlag, loginExtraData=$loginExtraData, wFastLoginInfo=$wFastLoginInfo, reserveUinInfo=$reserveUinInfo, wLoginSigInfo=$wLoginSigInfo, tlv113=${tlv113?.contentToString()}, qrPushSig=${qrPushSig.contentToString()}, mainDisplayName='$mainDisplayName')" return "QQAndroidClient(account=$account, ecdh=$ecdh, device=$device, tgtgtKey=${tgtgtKey.contentToString()}, randomKey=${randomKey.contentToString()}, miscBitMap=$miscBitMap, mainSigMap=$mainSigMap, subSigMap=$subSigMap, openAppId=$openAppId, apkVersionName=${apkVersionName.contentToString()}, loginState=$loginState, appClientVersion=$appClientVersion, networkType=$networkType, apkSignatureMd5=${apkSignatureMd5.contentToString()}, protocolVersion=$protocolVersion, apkId=${apkId.contentToString()}, t150=${t150?.contentToString()}, rollbackSig=${rollbackSig?.contentToString()}, ipFromT149=${ipFromT149?.contentToString()}, timeDifference=$timeDifference, uin=$uin, t530=${t530?.contentToString()}, t528=${t528?.contentToString()}, ksid='$ksid', pwdFlag=$pwdFlag, loginExtraData=$loginExtraData, wFastLoginInfo=$wFastLoginInfo, reserveUinInfo=$reserveUinInfo, wLoginSigInfo=$wLoginSigInfo, tlv113=${tlv113?.contentToString()}, qrPushSig=${qrPushSig.contentToString()}, mainDisplayName='$mainDisplayName')"
} }
...@@ -95,7 +95,6 @@ internal open class QQAndroidClient( ...@@ -95,7 +95,6 @@ internal open class QQAndroidClient(
val apkVersionName: ByteArray = "8.2.0".toByteArray() val apkVersionName: ByteArray = "8.2.0".toByteArray()
var loginState = 0
val appClientVersion: Int = 0 val appClientVersion: Int = 0
...@@ -108,14 +107,14 @@ internal open class QQAndroidClient( ...@@ -108,14 +107,14 @@ internal open class QQAndroidClient(
*/ */
val protocolVersion: Short = 8001 val protocolVersion: Short = 8001
@Suppress("SpellCheckingInspection")
@PublishedApi
internal val apkId: ByteArray = "com.tencent.mobileqq".toByteArray()
/* /*
* 以下登录使用 * 以下登录使用
*/ */
@Suppress("SpellCheckingInspection")
@PublishedApi
internal val apkId: ByteArray = "com.tencent.mobileqq".toByteArray()
var loginState = 0
var t150: Tlv? = null var t150: Tlv? = null
var rollbackSig: ByteArray? = null var rollbackSig: ByteArray? = null
...@@ -129,8 +128,12 @@ internal open class QQAndroidClient( ...@@ -129,8 +128,12 @@ internal open class QQAndroidClient(
* *
* **注意**: 总是使用这个属性, 而不要使用 [BotAccount.id]. 将来它可能会变为 [String] * **注意**: 总是使用这个属性, 而不要使用 [BotAccount.id]. 将来它可能会变为 [String]
*/ */
@UseExperimental(MiraiExperimentalAPI::class, MiraiInternalAPI::class) val uin: Long get() = _uin
var uin: Long = bot.account.id
@UseExperimental(RawAccountIdUse::class)
@Suppress("PropertyName")
internal var _uin: Long = bot.account.id
var t530: ByteArray? = null var t530: ByteArray? = null
var t528: ByteArray? = null var t528: ByteArray? = null
/** /**
......
...@@ -95,6 +95,6 @@ internal interface EncryptMethodECDH : EncryptMethod { ...@@ -95,6 +95,6 @@ internal interface EncryptMethodECDH : EncryptMethod {
}) })
// encryptAndWrite("26 33 BA EC 86 EB 79 E6 BC E0 20 06 5E A9 56 6C".hexToBytes(), body) // encryptAndWrite("26 33 BA EC 86 EB 79 E6 BC E0 20 06 5E A9 56 6C".hexToBytes(), body)
encryptAndWrite(ecdh.keyPair.shareKey, body) encryptAndWrite(ecdh.keyPair.initialShareKey, body)
} }
} }
\ No newline at end of file
...@@ -7,8 +7,6 @@ import kotlinx.io.core.buildPacket ...@@ -7,8 +7,6 @@ import kotlinx.io.core.buildPacket
import kotlinx.io.core.writeFully import kotlinx.io.core.writeFully
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.cryptor.DecrypterByteArray
import net.mamoe.mirai.utils.cryptor.encryptAndWrite
import net.mamoe.mirai.utils.io.encryptAndWrite import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.utils.io.writeHex import net.mamoe.mirai.utils.io.writeHex
import net.mamoe.mirai.utils.io.writeIntLVPacket import net.mamoe.mirai.utils.io.writeIntLVPacket
...@@ -26,7 +24,7 @@ internal class OutgoingPacket constructor( ...@@ -26,7 +24,7 @@ internal class OutgoingPacket constructor(
val delegate: ByteReadPacket val delegate: ByteReadPacket
) { ) {
val name: String by lazy { val name: String by lazy {
name ?: commandName.toString() name ?: commandName
} }
} }
...@@ -50,22 +48,13 @@ internal val EMPTY_BYTE_ARRAY = ByteArray(0) ...@@ -50,22 +48,13 @@ internal val EMPTY_BYTE_ARRAY = ByteArray(0)
* *
* byte[] body encrypted by 16 zero * byte[] body encrypted by 16 zero
*/ */
/*
* 00 00 02 34 // remaining.length + 4
* 00 00 00 0B
* 01
* 00 01 4E 73 // sequence
* 00
* 00 00 00 0E
* 31 39 39 34 37 30 31 30 32 31
* 18 5D 8F 17 7D 67 71 61 FE DB 30 A4 4D 16 DD 0E 8D 84 0A F2 44 BE FB BB 11 BB B4 AC 79 50 50 9F 4C 99 CC 77 0B AA B6 E0 06 0C F7 91 79 99 57 31 3D EF 38 92 2C C8 81 33 79 83 FF C6 2F BA 18 2A 33 F8 D9 4E CD 62 07 D8 08 B7 1A 1E C7 EB AC AB B4 1E C9 9D A9 15 9C 29 29 2A 99 F6 BB D0 43 65 D6 5E 9C 93 A8 8D 17 08 5B 6A 29 92 58 6A 75 C9 B5 45 B3 0E A5 D3 52 8F 9A A4 88 36 A0 14 3A 21 F2 46 C3 91 66 A3 73 67 6A 3E F7 9D 8E 44 52 87 7B 8A C7 1B E2 D3 98 62 E8 25 30 2A 43 5C 5A B2 C6 45 F5 39 EC 85 81 BF 7D 22 4C E8 01 87 92 48 38 06 6B A0 83 70 0B 51 ED CF 7A FF E2 F2 06 3E A7 95 4E E5 29 23 32 1C FE 79 C6 08 C5 7A 39 B9 AF CD 4F 80 3E 5D 74 4D 0B E1 10 33 8D F0 54 8E 0E 22 96 B4 06 7F 29 01 1E CA 30 35 FD 8A 2E 51 04 20 79 7B 08 DC DF F6 64 21 6B C5 95 34 B3 40 D2 E8 CE BB DC 69 89 75 62 A6 0B 4A 49 9D 90 BA 68 2B BD 8A 50 2D 68 6B 56 40 0C 39 F2 08 20 1B EB A4 A5 20 1D 1F 7E FA 4B B8 2E 58 79 2A 16 54 26 6C C8 44 6C 4F 64 2D 5C 0C 47 2E 90 13 A9 D7 33 4A 51 17 6E 3F 3E 48 AE 39 D8 45 05 2C 0C 3C 9F 92 39 DB 62 B3 BB 64 EE 7E 91 C5 84 92 10 96 D9 F1 13 02 94 00 EA DA 87 7C 85 7B 68 BA 8D A1 AB F5 CD 9C EB 4C CD A0 38 78 43 80 DD E5 1D 28 25 1F F0 25 EF 0D 95 91 0F 21 5D 41 06 00 03 48 77 E0 98 09 3E 04 5A B0 93 63 3B AE 8E 49 0C C2 12 BA DD C3 5A ED FF 68 98 22 C4 5E F6 1E 85 57 15 E8 7E 26 22 E3 70 C2 57 F4 CE 2F CB C4 DC 39 4A 9C FE DE 27 18 D3 36 66 88 92 D7 69 D0 04 8E 93 9B AD E9 2E 5A 2C 91 CD 28 DF BE 62 CF 2C 72 8E FD A9 1F 0E 8E 00 9E 54 28 50 25 0C E7 DC 98 85 C9 B3 59 A8 97 F5 2E 7F 44 4C 43 3C C4 65 E5 AB DB 5B 3C 50 2D 53 B3 EA 74 3C 39 F4 0A 52 31 34 30 F5 E6 82 CD 36 D9
*/
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
internal inline fun PacketFactory<*>.buildOutgingPacket( internal inline fun PacketFactory<*>.buildOutgoingPacket(
client: QQAndroidClient, client: QQAndroidClient,
bodyType: Byte = 1, // 1: PB?
name: String? = this.commandName, name: String? = this.commandName,
commandName: String = this.commandName, commandName: String = this.commandName,
key: ByteArray, key: ByteArray = client.wLoginSigInfo.d2Key,
body: BytePacketBuilder.(sequenceId: Int) -> Unit body: BytePacketBuilder.(sequenceId: Int) -> Unit
): OutgoingPacket { ): OutgoingPacket {
val sequenceId: Int = client.nextSsoSequenceId() val sequenceId: Int = client.nextSsoSequenceId()
...@@ -73,7 +62,7 @@ internal inline fun PacketFactory<*>.buildOutgingPacket( ...@@ -73,7 +62,7 @@ internal inline fun PacketFactory<*>.buildOutgingPacket(
return OutgoingPacket(name, commandName, sequenceId, buildPacket { return OutgoingPacket(name, commandName, sequenceId, buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) { writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(0x0B) writeInt(0x0B)
writeByte(1) writeByte(bodyType)
writeInt(sequenceId) writeInt(sequenceId)
writeByte(0) writeByte(0)
client.uin.toString().let { client.uin.toString().let {
...@@ -87,6 +76,68 @@ internal inline fun PacketFactory<*>.buildOutgingPacket( ...@@ -87,6 +76,68 @@ internal inline fun PacketFactory<*>.buildOutgingPacket(
}) })
} }
/**
* buildOutgoingPacket 与 writeUniPacket 的 fast-path
*/
@UseExperimental(MiraiInternalAPI::class)
internal inline fun PacketFactory<*>.buildOutgoingUniPacket(
client: QQAndroidClient,
bodyType: Byte = 1, // 1: PB?
name: String? = this.commandName,
commandName: String = this.commandName,
key: ByteArray = client.wLoginSigInfo.d2Key,
extraData: ByteReadPacket = BRP_STUB,
body: BytePacketBuilder.(sequenceId: Int) -> Unit
): OutgoingPacket {
val sequenceId: Int = client.nextSsoSequenceId()
return OutgoingPacket(name, commandName, sequenceId, buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(0x0B)
writeByte(bodyType)
writeInt(sequenceId)
writeByte(0)
client.uin.toString().let {
writeInt(it.length + 4)
writeStringUtf8(it)
}
encryptAndWrite(key) {
writeUniPacket(commandName, extraData) {
body(sequenceId)
}
}
}
})
}
@UseExperimental(MiraiInternalAPI::class)
internal inline fun BytePacketBuilder.writeUniPacket(
commandName: String,
extraData: ByteReadPacket = BRP_STUB,
body: BytePacketBuilder.() -> Unit
) {
writeIntLVPacket(lengthOffset = { it + 4 }) {
commandName.let {
writeInt(it.length + 4)
writeStringUtf8(it)
}
writeInt(4 + 4)
writeInt(45112203) // 02 B0 5B 8B
if (extraData === BRP_STUB) {
writeInt(0x04)
} else {
writeInt((extraData.remaining + 4).toInt())
writePacket(extraData)
}
}
// body
writeIntLVPacket(lengthOffset = { it + 4 }, builder = body)
}
/** /**
* 最外层的包. 结构适用于登录. * 最外层的包. 结构适用于登录.
* *
...@@ -213,37 +264,6 @@ internal inline fun BytePacketBuilder.writeSsoPacket( ...@@ -213,37 +264,6 @@ internal inline fun BytePacketBuilder.writeSsoPacket(
} }
/**
* Outermost packet, not for login
*
* **Packet structure**
* int remaining.length + 4
* int 0x0B
* byte 0x01
* byte 0
* int [uinAccount].length + 4
* byte[] uinAccount
*
* byte[] body encrypted by [sessionKey]
*/
internal inline fun PacketFactory<*>.buildSessionOutgoingPacket(
uinAccount: String,
sessionKey: DecrypterByteArray,
body: BytePacketBuilder.() -> Unit
): ByteReadPacket = buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(0x00_00_00_0B)
writeByte(0x01)
writeByte(0)
writeInt(uinAccount.length + 4)
writeStringUtf8(uinAccount)
encryptAndWrite(sessionKey, body)
}
}
/** /**
* Writes a request packet * Writes a request packet
* This is the innermost packet structure * This is the innermost packet structure
...@@ -293,6 +313,8 @@ internal fun BytePacketBuilder.writeOicqRequestPacket( ...@@ -293,6 +313,8 @@ internal fun BytePacketBuilder.writeOicqRequestPacket(
writeByte(0x03) // tail writeByte(0x03) // tail
// } // }
} }
/* /*
00 00 01 64 00 00 01 64
00 00 00 0A 00 00 00 0A
......
...@@ -2,7 +2,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data ...@@ -2,7 +2,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf import net.mamoe.mirai.qqandroid.io.ProtoBuf
@Serializable @Serializable
internal class Cmd0x352Packet( internal class Cmd0x352Packet(
......
...@@ -3,7 +3,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data ...@@ -3,7 +3,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.utils.currentTimeSeconds import net.mamoe.mirai.utils.currentTimeSeconds
interface ImgReq : ProtoBuf interface ImgReq : ProtoBuf
......
@file:Suppress("ArrayInDataClass")
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumberType
import kotlinx.serialization.protobuf.ProtoType
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf
class MessageCommon {
/**
* 1 -> varint
* 2 -> delimi
* 3 -> varint
* 4 -> varint
* 5 -> varint
* 6 -> varint
* 7 -> delimi
* 8 -> delimi
* 9 -> delimi
* 10 -> delimi
* 11 -> delimi
*/
@Serializable
data class PluginInfo(
@SerialId(1) val resId: Int = 0,
@SerialId(2) val packageName: String = "",
@SerialId(3) val newVer: Int = 0,
@SerialId(4) val resType: Int = 0,
@SerialId(5) val lanType: Int = 0,
@SerialId(6) val priority: Int = 0,
@SerialId(7) val resName: String = "",
@SerialId(8) val resDesc: String = "",
@SerialId(9) val resUrlBig: String = "",
@SerialId(10) val resUrlSmall: String = "",
@SerialId(11) val resConf: String = ""
) : ProtoBuf
@Serializable
data class AppShareInfo(
@ProtoType(ProtoNumberType.FIXED) @SerialId(1) val id: Int = 0,
@SerialId(2) val cookie: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val resource: PluginInfo = PluginInfo()
) : ProtoBuf
@Serializable
data class ContentHead(
@SerialId(1) val pkgNum: Int = 0,
@SerialId(2) val pkgIndex: Int = 0,
@SerialId(3) val divSeq: Int = 0,
@SerialId(4) val autoReply: Int = 0
) : ProtoBuf
@Serializable
data class Msg(
val s: String
) : ProtoBuf
}
\ No newline at end of file
...@@ -2,8 +2,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data ...@@ -2,8 +2,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf
/** /**
* msf.msgcomm.msg_comm * msf.msgcomm.msg_comm
...@@ -60,7 +60,7 @@ class MsgComm : ProtoBuf { ...@@ -60,7 +60,7 @@ class MsgComm : ProtoBuf {
@SerialId(1) val groupCode: Long = 0L, @SerialId(1) val groupCode: Long = 0L,
@SerialId(2) val groupType: Int = 0, @SerialId(2) val groupType: Int = 0,
@SerialId(3) val groupInfoSeq: Long = 0L, @SerialId(3) val groupInfoSeq: Long = 0L,
@SerialId(4) val groupCard: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(4) val groupCard: String = "",
@SerialId(5) val groupRank: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(5) val groupRank: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(6) val groupLevel: Int = 0, @SerialId(6) val groupLevel: Int = 0,
@SerialId(7) val groupCardType: Int = 0, @SerialId(7) val groupCardType: Int = 0,
...@@ -69,9 +69,9 @@ class MsgComm : ProtoBuf { ...@@ -69,9 +69,9 @@ class MsgComm : ProtoBuf {
@Serializable @Serializable
class Msg( class Msg(
@SerialId(1) val msgHead: MsgHead? = null, @SerialId(1) val msgHead: MsgHead,
@SerialId(2) val contentHead: ContentHead? = null, @SerialId(2) val contentHead: ContentHead? = null,
@SerialId(3) val msgBody: ImMsgBody.MsgBody? = null, @SerialId(3) val msgBody: ImMsgBody.MsgBody,
@SerialId(4) val appshareInfo: AppShareInfo? = null @SerialId(4) val appshareInfo: AppShareInfo? = null
) : ProtoBuf ) : ProtoBuf
...@@ -145,7 +145,7 @@ class MsgComm : ProtoBuf { ...@@ -145,7 +145,7 @@ class MsgComm : ProtoBuf {
@SerialId(1) val lastReadTime: Int = 0, @SerialId(1) val lastReadTime: Int = 0,
@SerialId(2) val peerUin: Long = 0L, @SerialId(2) val peerUin: Long = 0L,
@SerialId(3) val msgCompleted: Int = 0, @SerialId(3) val msgCompleted: Int = 0,
@SerialId(4) val msg: List<Msg>? = null, @SerialId(4) val msg: List<Msg>,
@SerialId(5) val unreadMsgNum: Int = 0, @SerialId(5) val unreadMsgNum: Int = 0,
@SerialId(8) val c2cType: Int = 0, @SerialId(8) val c2cType: Int = 0,
@SerialId(9) val serviceType: Int = 0, @SerialId(9) val serviceType: Int = 0,
......
...@@ -2,14 +2,14 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data ...@@ -2,14 +2,14 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf
@Serializable @Serializable
class MsgOnlinePush { class MsgOnlinePush {
@Serializable @Serializable
data class PbPushMsg( class PbPushMsg(
@SerialId(1) val msg: MsgComm.Msg? = null, @SerialId(1) val msg: MsgComm.Msg,
@SerialId(2) val svrip: Int = 0, @SerialId(2) val svrip: Int = 0,
@SerialId(3) val pushToken: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(3) val pushToken: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val pingFlag: Int = 0, @SerialId(4) val pingFlag: Int = 0,
......
...@@ -6,8 +6,9 @@ import net.mamoe.mirai.data.Packet ...@@ -6,8 +6,9 @@ import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
@Suppress("ArrayInDataClass")
@Serializable @Serializable
internal class RequestPushNotify( internal data class RequestPushNotify(
@SerialId(0) val uin: Long = 0L, @SerialId(0) val uin: Long = 0L,
@SerialId(1) val ctype: Byte = 0, @SerialId(1) val ctype: Byte = 0,
@SerialId(2) val strService: String?, @SerialId(2) val strService: String?,
......
...@@ -2,22 +2,25 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive ...@@ -2,22 +2,25 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.serialization.SerialId import net.mamoe.mirai.data.MultiPacket
import kotlinx.serialization.Serializable import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.readRemainingAsProtoBuf
import net.mamoe.mirai.qqandroid.io.serialization.loadAs import net.mamoe.mirai.qqandroid.io.serialization.loadAs
import net.mamoe.mirai.qqandroid.io.serialization.readRemainingAsJceStruct import net.mamoe.mirai.qqandroid.io.serialization.readRemainingAsJceStruct
import net.mamoe.mirai.qqandroid.io.writeProtoBuf
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion2 import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.MsgSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.RequestPushNotify import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.RequestPushNotify
import net.mamoe.mirai.utils.cryptor.contentToString import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestDataVersion2
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestPacket
import net.mamoe.mirai.qqandroid.utils.toMessageChain
import net.mamoe.mirai.utils.firstValue import net.mamoe.mirai.utils.firstValue
import net.mamoe.mirai.utils.io.debugPrint import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.toReadPacket import net.mamoe.mirai.utils.io.toReadPacket
class MessageSvc { class MessageSvc {
...@@ -25,31 +28,64 @@ class MessageSvc { ...@@ -25,31 +28,64 @@ class MessageSvc {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): RequestPushNotify { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): RequestPushNotify {
discardExact(8) discardExact(8)
@Serializable return readRemainingAsJceStruct(RequestPacket.serializer()).sBuffer
class ResponseDataRequestPushNotify(
@SerialId(0) val notify: RequestPushNotify
) : JceStruct
val requestPushNotify = readRemainingAsJceStruct(RequestPacket.serializer()).sBuffer
.loadAs(RequestDataVersion2.serializer()).map.firstValue().firstValue() .loadAs(RequestDataVersion2.serializer()).map.firstValue().firstValue()
.toReadPacket().apply { discardExact(1) } .toReadPacket().apply { discardExact(1) }
.debugPrint()
.readRemainingAsJceStruct(RequestPushNotify.serializer()) .readRemainingAsJceStruct(RequestPushNotify.serializer())
}
println(requestPushNotify.contentToString()) override suspend fun QQAndroidBot.handle(packet: RequestPushNotify) {
network.run {
with(bot.network) { PbGetMsg(client, packet).sendAndExpect<MultiPacket<FriendMessage>>()
GetMsgRequest(
bot.client,
requestPushNotify
).sendAndExpect<MsgSvc.PbGetMsgResp>()
} }
return requestPushNotify
} }
} }
internal object PbGetMsg : PacketFactory<MultiPacket<FriendMessage>>("MessageSvc.PbGetMsg") {
operator fun invoke(
client: QQAndroidClient,
from: RequestPushNotify
): OutgoingPacket = buildOutgoingUniPacket(
client,
extraData = "08 00 12 33 6D 6F 64 65 6C 3A 78 69 61 6F 6D 69 20 36 3B 6F 73 3A 32 32 3B 76 65 72 73 69 6F 6E 3A 76 32 6D 61 6E 3A 78 69 61 6F 6D 69 73 79 73 3A 4C 4D 59 34 38 5A 18 E4 E1 A4 FF FE 2D 20 E9 E1 A4 FF FE 2D 28 A8 E1 A4 FF FE 2D 30 99 E1 A4 FF FE 2D".hexToBytes().toReadPacket()
) {
writeProtoBuf(
MsgSvc.PbGetMsgReq.serializer(),
MsgSvc.PbGetMsgReq(
msgReqType = from.ctype.toInt(),
contextFlag = 1,
rambleFlag = 0,
latestRambleNumber = 20,
otherRambleNumber = 3,
onlineSyncFlag = 1,
serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY
)
)
}
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): MultiPacket<FriendMessage> {
// 00 00 01 0F 08 00 12 00 1A 34 08 FF C1 C4 F1 05 10 FF C1 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 8A CA 91 D1 0C 48 9B A5 BD 9B 0A 58 DE 9D 99 F8 08 60 1D 68 FF C1 C4 F1 05 70 00 20 02 2A 9D 01 08 F3 C1 C4 F1 05 10 A2 FF 8C F0 03 18 01 22 8A 01 0A 2A 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 A6 01 20 0B 28 AE F9 01 30 F4 C1 C4 F1 05 38 A7 E3 D8 D4 84 80 80 80 01 B8 01 CD B5 01 12 08 08 01 10 00 18 00 20 00 1A 52 0A 50 0A 27 08 00 10 F4 C1 C4 F1 05 18 A7 E3 D8 D4 04 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 08 0A 06 0A 04 4E 4D 53 4C 12 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 12 04 4A 02 08 00 30 01 2A 15 08 97 A2 C1 F1 05 10 95 A6 F5 E5 0C 18 01 30 01 40 01 48 81 01 2A 10 08 D3 F7 B5 F1 05 10 DD F1 92 B7 07 18 01 30 01 38 00 42 00 48 00
discardExact(4)
val resp = readRemainingAsProtoBuf(MsgSvc.PbGetMsgResp.serializer())
//println(resp.contentToString())
if (resp.uinPairMsgs == null) {
return MultiPacket(emptyList())
}
return MultiPacket(resp.uinPairMsgs.asSequence().flatMap { it.msg.asSequence() }.mapNotNull {
when (it.msgHead.msgType) {
166 -> {
FriendMessage(
bot,
false, // TODO: 2020/1/29 PREVIOUS??
bot.getQQ(it.msgHead.fromUin),
it.msgBody.richText.toMessageChain()
)
}
else -> null
}
}.toList())
}
}
} }
...@@ -7,26 +7,12 @@ import kotlinx.io.core.discardExact ...@@ -7,26 +7,12 @@ import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes import kotlinx.io.core.readBytes
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.data.ImageLink
import net.mamoe.mirai.message.GroupMessage 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.MessageChain
import net.mamoe.mirai.message.data.toMessage
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.ImMsgBody import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.MsgOnlinePush import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.MsgOnlinePush
import net.mamoe.mirai.utils.io.encodeToString import net.mamoe.mirai.qqandroid.utils.toMessageChain
internal class ImageIdQQA(
override val value: String,
originalLink: String
) : ImageId {
val link: ImageLink = ImageLinkQQA("http://gchat.qpic.cn$originalLink")
}
internal inline class ImageLinkQQA(override val original: String) : ImageLink
internal class OnlinePush { internal class OnlinePush {
internal object PbPushGroupMsg : PacketFactory<GroupMessage>("OnlinePush.PbPushGroupMsg") { internal object PbPushGroupMsg : PacketFactory<GroupMessage>("OnlinePush.PbPushGroupMsg") {
...@@ -35,27 +21,18 @@ internal class OnlinePush { ...@@ -35,27 +21,18 @@ internal class OnlinePush {
// 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00 // 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00
discardExact(4) discardExact(4)
val pbPushMsg = ProtoBuf.load(MsgOnlinePush.PbPushMsg.serializer(), readBytes()) val pbPushMsg = ProtoBuf.load(MsgOnlinePush.PbPushMsg.serializer(), readBytes())
val message = MessageChain(initialCapacity = pbPushMsg.msg!!.msgBody!!.richText!!.elems!!.size)
var extraInfo: ImMsgBody.ExtraInfo? = null val extraInfo: ImMsgBody.ExtraInfo? = pbPushMsg.msg.msgBody.richText.elems.firstOrNull { it.extraInfo != null }?.extraInfo
pbPushMsg.msg.msgBody!!.richText!!.elems!!.forEach {
when {
it.customFace != null -> message.add(Image(ImageIdQQA(it.customFace.filePath, it.customFace.origUrl)))
it.text != null -> message.add(it.text.str.encodeToString().toMessage())
it.extraInfo != null -> extraInfo = it.extraInfo
}
}
val group = bot.getGroup(pbPushMsg.msg.msgHead!!.groupInfo!!.groupCode) val group = bot.getGroup(pbPushMsg.msg.msgHead.groupInfo!!.groupCode)
val flags = extraInfo?.flags ?: 0 val flags = extraInfo?.flags ?: 0
return GroupMessage( return GroupMessage(
bot = bot, bot = bot,
group = group, group = group,
senderName = pbPushMsg.msg.msgHead.groupInfo!!.groupCard.encodeToString(), senderName = pbPushMsg.msg.msgHead.groupInfo.groupCard,
sender = group.getMember(pbPushMsg.msg.msgHead.fromUin), sender = group.getMember(pbPushMsg.msg.msgHead.fromUin),
message = message, message = pbPushMsg.msg.msgBody.richText.toMessageChain(),
permission = when { permission = when {
flags and 16 != 0 -> MemberPermission.ADMINISTRATOR flags and 16 != 0 -> MemberPermission.ADMINISTRATOR
flags and 8 != 0 -> MemberPermission.OWNER flags and 8 != 0 -> MemberPermission.OWNER
......
...@@ -46,7 +46,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt ...@@ -46,7 +46,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
} }
} }
object SubCommand8 { object SubCommand7 {
private const val appId = 16L private const val appId = 16L
private const val subAppId = 537062845L private const val subAppId = 537062845L
...@@ -59,26 +59,16 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt ...@@ -59,26 +59,16 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) { writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) { writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) {
writeShort(8) // subCommand writeShort(7) // subCommand
writeShort(6) // count of TLVs, probably ignored by server?TODO writeShort(7) // count of TLVs, probably ignored by server?TODO
t8(2052) t8(2052)
t104(client.t104) t104(client.t104)
t116(150470524, 66560) t116(150470524, 66560)
t174(t174) t174(t174)
t17a(9) t17c(phoneNumber.toByteArray())
t197(byteArrayOf(0.toByte())) t401(md5(client.device.guid + "1234567890123456".toByteArray() + t402))
//t401(md5(client.device.guid + "12 34567890123456".toByteArray() + t402)) t19e(0)//==tlv408
//t19e(0)//==tlv408
} }
/**
*
* 0x00000008(8)=00 00 00 00 08 04 00 00,//2052固定
0x00000104(260)=41 69 78 39 46 68 4E 44 6C 41 42 30 54 79 46 30 4B 36 67 78 37 45 6E 2B 30 7A 39 35 65 35 30 6E 66 41 3D 3D,//服务器给的tv104
0x00000116(278)=00 08 F7 FF 7C 00 01 04 00 01 5F 5E 10 E2//116(this.mMiscBitmap, this.mSubSigMap, var10._sub_appid_list)
0x00000174(372)=45 66 43 39 46 4B 63 70 47 30 5F 5A 55 41 4F 6A 4E 4C 6F 72 56 30 77 66 4B 67 49 4D 33 33 6E 58 44 37 5F 4B 61 75 56 6D 4F 6F 54 68 6A 64 38 62 72 44 64 69 5F 62 48 51 5A 66 37 6E 4F 6B 78 43 35 6E 47 4E 38 6B 6A 35 39 6D 37 32 71 47 66 78 4E 76 50 51 53 39 33 66 37 6B 72 71 66 71 78 63 5F//服务器给的tv174
0x0000017A(378)=00 00 00 09, //9 固定
0x00000197(407)=00//固定
*/
} }
} }
} }
......
...@@ -7,10 +7,10 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot ...@@ -7,10 +7,10 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.io.serialization.writeJceStruct import net.mamoe.mirai.qqandroid.io.serialization.writeJceStruct
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataStructSvcReqRegister import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestDataStructSvcReqRegister
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion3 import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestDataVersion3
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.jce.SvcReqRegister import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.SvcReqRegister
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket
......
package net.mamoe.mirai.qqandroid.network.protocol.jce package net.mamoe.mirai.qqandroid.network.protocol.packet.login.data
import kotlinx.serialization.Polymorphic
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
......
package net.mamoe.mirai.qqandroid.network.protocol.jce package net.mamoe.mirai.qqandroid.network.protocol.packet.login.data
import kotlinx.serialization.Polymorphic import kotlinx.serialization.Polymorphic
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
......
...@@ -2,7 +2,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.oidb.oidb0x769 ...@@ -2,7 +2,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.oidb.oidb0x769
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf import net.mamoe.mirai.qqandroid.io.ProtoBuf
class Oidb0x769 { class Oidb0x769 {
@Serializable @Serializable
......
package net.mamoe.mirai.qqandroid.network.protocol.protobuf
/**
* 仅有标示作用
*/
interface ProtoBuf {
}
\ No newline at end of file
package net.mamoe.mirai.qqandroid.utils
import net.mamoe.mirai.data.ImageLink
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.MsgSvc
internal fun MessageChain.constructPbSendMsgReq(): MsgSvc.PbSendMsgReq {
val request = MsgSvc.PbSendMsgReq()
this.forEach {
when (it) {
is PlainText -> {
request.msgBody.richText.elems.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue)))
}
is At -> {
}
}
}
return request
}
internal fun ImMsgBody.RichText.toMessageChain() : MessageChain{
val message = MessageChain(initialCapacity = elems.size)
elems.forEach {
when {
it.notOnlineImage != null -> message.add(Image(
ImageIdQQA(
it.notOnlineImage.resId,
it.notOnlineImage.origUrl
)
))
it.customFace != null -> message.add(Image(
ImageIdQQA(
it.customFace.filePath,
it.customFace.origUrl
)
))
it.text != null -> message.add(it.text.str.toMessage())
}
}
return message
}
internal class ImageIdQQA(
override val value: String,
originalLink: String
) : ImageId {
val link: ImageLink =
ImageLinkQQA("http://gchat.qpic.cn$originalLink")
}
internal inline class ImageLinkQQA(override val original: String) : ImageLink
\ No newline at end of file
package net.mamoe.mirai.qqandroid.io.serialization package net.mamoe.mirai.qqandroid.io.serialization
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestPacket
import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.hexToBytes
class TestRequesetPacket { class TestRequesetPacket {
......
...@@ -143,7 +143,7 @@ fun String.generateProtoBufDataClass(): GeneratedClass { ...@@ -143,7 +143,7 @@ fun String.generateProtoBufDataClass(): GeneratedClass {
val javaClassname = substringBetween("class", "{") val javaClassname = substringBetween("class", "{")
val superclasses = javaClassname.split("$").map { it.trim().adjustClassName() }.toMutableList().apply { removeAt(this.lastIndex) } val superclasses = javaClassname.split("$").map { it.trim().adjustClassName() }.toMutableList().apply { removeAt(this.lastIndex) }
val className = substringBetween("class", "{").substringAfterLast("$").trim().adjustClassName() val className = substringBetween("class", "{").substringAfterLast("$").trim().adjustClassName()
return GeneratedClass(superclasses, className, "@Serializable\nclass $className") return GeneratedClass(superclasses, className, "@Serializable\nclass $className : ProtoBuf")
} }
val superclasses = substringBetween("class", "extends").split("$").map { it.trim().adjustClassName() }.toMutableList() val superclasses = substringBetween("class", "extends").split("$").map { it.trim().adjustClassName() }.toMutableList()
...@@ -154,7 +154,7 @@ fun String.generateProtoBufDataClass(): GeneratedClass { ...@@ -154,7 +154,7 @@ fun String.generateProtoBufDataClass(): GeneratedClass {
val ids = substringBetween("new int[]{", "}").split(",").map { it.trim() } val ids = substringBetween("new int[]{", "}").split(",").map { it.trim() }
if (ids.all { it.isBlank() }) { if (ids.all { it.isBlank() }) {
return GeneratedClass(superclasses, className, "@Serializable\nclass $className") return GeneratedClass(superclasses, className, "@Serializable\nclass $className : ProtoBuf")
} }
val names = substringBetween("new String[]{", "}").split(",").map { it.trim() } val names = substringBetween("new String[]{", "}").split(",").map { it.trim() }
...@@ -326,7 +326,7 @@ fun String.generateProtoBufDataClass(): GeneratedClass { ...@@ -326,7 +326,7 @@ fun String.generateProtoBufDataClass(): GeneratedClass {
append("\n") append("\n")
} }
append(")") append(") : ProtoBuf")
} }
return GeneratedClass(superclasses, className, source) return GeneratedClass(superclasses, className, source)
......
...@@ -5,6 +5,8 @@ import io.ktor.client.engine.cio.CIO ...@@ -5,6 +5,8 @@ import io.ktor.client.engine.cio.CIO
import io.ktor.util.KtorExperimentalAPI import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.io.ByteArrayPool
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.DataInput import java.io.DataInput
import java.io.EOFException import java.io.EOFException
...@@ -83,17 +85,23 @@ actual fun crc32(key: ByteArray): Int = CRC32().apply { update(key) }.value.toIn ...@@ -83,17 +85,23 @@ actual fun crc32(key: ByteArray): Int = CRC32().apply { update(key) }.value.toIn
*/ */
actual fun solveIpAddress(hostname: String): String = InetAddress.getByName(hostname).hostAddress actual fun solveIpAddress(hostname: String): String = InetAddress.getByName(hostname).hostAddress
actual fun ByteArray.unzip(): ByteArray { actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray {
this.checkOffsetAndLength(offset, length)
if (length == 0) return ByteArray(0)
val inflater = Inflater() val inflater = Inflater()
inflater.reset() inflater.reset()
val output = ByteArrayOutputStream() ByteArrayOutputStream().use { output ->
inflater.setInput(this) inflater.setInput(this, offset, length)
val buffer = ByteArray(128) ByteArrayPool.useInstance {
while (!inflater.finished()) { while (!inflater.finished()) {
output.write(buffer, 0, inflater.inflate(buffer)) output.write(it, 0, inflater.inflate(it))
}
}
inflater.end()
return output.toByteArray()
} }
inflater.end()
return output.toByteArray()
} }
actual fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher { actual fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher {
......
...@@ -15,7 +15,7 @@ actual class ECDHKeyPair( ...@@ -15,7 +15,7 @@ actual class ECDHKeyPair(
actual val privateKey: ECDHPrivateKey get() = delegate.private actual val privateKey: ECDHPrivateKey get() = delegate.private
actual val publicKey: ECDHPublicKey get() = delegate.public actual val publicKey: ECDHPublicKey get() = delegate.public
actual val shareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey) actual val initialShareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey)
} }
@Suppress("FunctionName") @Suppress("FunctionName")
......
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
package net.mamoe.mirai package net.mamoe.mirai
import kotlinx.io.core.toByteArray import kotlinx.io.core.toByteArray
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.md5 import net.mamoe.mirai.utils.md5
import kotlin.annotation.AnnotationTarget.* import kotlin.annotation.AnnotationTarget.*
...@@ -12,17 +10,17 @@ data class BotAccount( ...@@ -12,17 +10,17 @@ data class BotAccount(
/** /**
* **注意**: 在 Android 协议, 总是使用 `QQAndroidClient.uin` 或 [Bot.uin], 而不要使用 [BotAccount.id]. 将来 [BotAccount.id] 可能会变为 [String] * **注意**: 在 Android 协议, 总是使用 `QQAndroidClient.uin` 或 [Bot.uin], 而不要使用 [BotAccount.id]. 将来 [BotAccount.id] 可能会变为 [String]
*/ */
@MiraiExperimentalAPI @RawAccountIdUse
val id: Long, val id: Long,
val passwordMd5: ByteArray // md5 val passwordMd5: ByteArray // md5
){ ) {
constructor(id: Long, passwordPlainText: String) : this(id, md5(passwordPlainText.toByteArray())) constructor(id: Long, passwordPlainText: String) : this(id, md5(passwordPlainText.toByteArray()))
} }
/** /**
* 标记直接访问 [BotAccount.id], 而不是访问 [Bot.uin]. 这可能会不兼容未来的 API 修改. * 标记直接访问 [BotAccount.id], 而不是访问 [Bot.uin]. 这可能会不兼容未来的 API 修改.
*/ */
@Retention(AnnotationRetention.SOURCE) @Retention(AnnotationRetention.SOURCE)
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
@Experimental @Experimental(level = Experimental.Level.WARNING)
annotation class RawAccountIdUse annotation class RawAccountIdUse
\ No newline at end of file
...@@ -13,6 +13,7 @@ import kotlin.coroutines.CoroutineContext ...@@ -13,6 +13,7 @@ import kotlin.coroutines.CoroutineContext
/* /*
* 泛型 N 不需要向外(接口)暴露. * 泛型 N 不需要向外(接口)暴露.
*/ */
@UseExperimental(MiraiExperimentalAPI::class)
@MiraiInternalAPI @MiraiInternalAPI
abstract class BotImpl<N : BotNetworkHandler> constructor( abstract class BotImpl<N : BotNetworkHandler> constructor(
account: BotAccount, account: BotAccount,
...@@ -25,10 +26,9 @@ abstract class BotImpl<N : BotNetworkHandler> constructor( ...@@ -25,10 +26,9 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
@Suppress("CanBePrimaryConstructorProperty") // for logger @Suppress("CanBePrimaryConstructorProperty") // for logger
final override val account: BotAccount = account final override val account: BotAccount = account
@UseExperimental(MiraiExperimentalAPI::class) @UseExperimental(RawAccountIdUse::class)
final override val uin: Long override val uin: Long get() = account.id
get() = account.id final override val logger: MiraiLogger by lazy { configuration.logger ?: DefaultLogger("Bot($uin)").also { configuration.logger = it } }
final override val logger: MiraiLogger = configuration.logger ?: DefaultLogger("Bot($uin)").also { configuration.logger = it }
init { init {
@Suppress("LeakingThis") @Suppress("LeakingThis")
......
...@@ -8,14 +8,14 @@ import net.mamoe.mirai.utils.coerceAtLeastOrFail ...@@ -8,14 +8,14 @@ import net.mamoe.mirai.utils.coerceAtLeastOrFail
/** /**
* 群. * 群. 在 QQ Android 中叫做 "Troop"
* *
* Group ID 与 Group Number 并不是同一个值. * Group ID 与 Group Number 并不是同一个值.
* - Group Number([Group.id]) 是通常使用的群号码.(在 QQ 客户端中可见) * - Group Number([Group.id]) 是通常使用的群号码.(在 QQ 客户端中可见)
* - Group ID([Group.internalId]) 是与调用 API 时使用的 id.(在 QQ 客户端中不可见) * - Group ID([Group.internalId]) 是与调用 API 时使用的 id.(在 QQ 客户端中不可见)
* @author Him188moe * @author Him188moe
*/ */
interface Group : Contact, CoroutineScope/*, Map<UInt, Member>*/ { // TODO: 2019/12/4 在 inline 稳定后实现 Map<UInt, Member>. 目前这样做会导致问题 interface Group : Contact, CoroutineScope/*, Map<UInt, Member>*/ { // TODO: 2020/1/29 实现接口 Map<Long, Memebr>
/** /**
* 内部 ID. 内部 ID 为 [GroupId] 的映射 * 内部 ID. 内部 ID 为 [GroupId] 的映射
*/ */
...@@ -86,7 +86,10 @@ fun Long.groupInternalId(): GroupInternalId = GroupInternalId(this) ...@@ -86,7 +86,10 @@ fun Long.groupInternalId(): GroupInternalId = GroupInternalId(this)
/** /**
* 将无符号整数格式的 [Long] 转为 [GroupId]. * 将无符号整数格式的 [Long] 转为 [GroupId].
* *
* 注: 在 Java 中常用 [Long] 来表示 [UInt] * 注: 在 Java 中常用 [Long] 来表示 [UInt].
*
* 注: 在 Kotlin/Java, 有符号的数据类型的二进制最高位为符号标志.
* 如一个 byte, `1000 0000` 最高位为 1, 则为负数.
*/ */
fun Long.groupId(): GroupId = GroupId(this.coerceAtLeastOrFail(0)) fun Long.groupId(): GroupId = GroupId(this.coerceAtLeastOrFail(0))
......
...@@ -4,3 +4,12 @@ package net.mamoe.mirai.data ...@@ -4,3 +4,12 @@ package net.mamoe.mirai.data
* 从服务器收到的包解析之后的结构化数据. * 从服务器收到的包解析之后的结构化数据.
*/ */
interface Packet interface Packet
/**
* PacketFactory 可以一次解析多个包出来. 它们将会被分别广播.
*/
class MultiPacket<P : Packet>(delegate: List<P>) : List<P> by delegate, Packet {
override fun toString(): String {
return "MultiPacket<${this.firstOrNull()?.let { it::class.simpleName }?: "?"}>"
}
}
\ No newline at end of file
...@@ -112,7 +112,7 @@ inline fun <reified E : Subscribable, T> CoroutineScope.subscribeUntil(valueIfSt ...@@ -112,7 +112,7 @@ inline fun <reified E : Subscribable, T> CoroutineScope.subscribeUntil(valueIfSt
* @see subscribe 获取更多说明 * @see subscribe 获取更多说明
*/ */
inline fun <reified E : Subscribable, T> CoroutineScope.subscribeWhile(valueIfContinue: T, crossinline listener: suspend E.(E) -> T): Listener<E> = inline fun <reified E : Subscribable, T> CoroutineScope.subscribeWhile(valueIfContinue: T, crossinline listener: suspend E.(E) -> T): Listener<E> =
E::class.subscribeInternal(Handler { if (it.listener(it) !== valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING }) E::class.subscribeInternal(Handler { if (it.listener(it) != valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
// endregion // endregion
......
...@@ -5,6 +5,7 @@ package net.mamoe.mirai.network ...@@ -5,6 +5,7 @@ package net.mamoe.mirai.network
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableJob import kotlinx.coroutines.CompletableJob
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.utils.io.PlatformDatagramChannel import net.mamoe.mirai.utils.io.PlatformDatagramChannel
...@@ -18,22 +19,26 @@ import net.mamoe.mirai.utils.io.PlatformDatagramChannel ...@@ -18,22 +19,26 @@ import net.mamoe.mirai.utils.io.PlatformDatagramChannel
* *
* [BotNetworkHandler] 的协程包含: * [BotNetworkHandler] 的协程包含:
* - UDP 包接收: [PlatformDatagramChannel.read] * - UDP 包接收: [PlatformDatagramChannel.read]
* - 心跳 Job [HeartbeatPacket] * - 心跳 Job
* - SKey 刷新 [RequestSKeyPacket] * - Key 刷新
* - 所有数据包处理和发送 * - 所有数据包处理和发送
* *
* [BotNetworkHandler.dispose] 时将会 [取消][kotlin.coroutines.CoroutineContext.cancelChildren] 所有此作用域下的协程 * [BotNetworkHandler.dispose] 时将会 [取消][Job.cancel] 所有此作用域下的协程
*
* A BotNetworkHandler is used to connect with Tencent servers.
*/ */
@Suppress("PropertyName") @Suppress("PropertyName")
abstract class BotNetworkHandler : CoroutineScope { abstract class BotNetworkHandler : CoroutineScope {
/**
* 所属 [Bot]. 为弱引用
*/
abstract val bot: Bot abstract val bot: Bot
/**
* 监管 child [Job]s
*/
abstract val supervisor: CompletableJob abstract val supervisor: CompletableJob
/** /**
* 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回登录结果 * 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回.
* 本函数将挂起直到登录成功. * 本函数将挂起直到登录成功.
*/ */
abstract suspend fun login() abstract suspend fun login()
...@@ -47,21 +52,12 @@ abstract class BotNetworkHandler : CoroutineScope { ...@@ -47,21 +52,12 @@ abstract class BotNetworkHandler : CoroutineScope {
* 关闭网络接口, 停止所有有关协程和任务 * 关闭网络接口, 停止所有有关协程和任务
*/ */
open fun dispose(cause: Throwable? = null) { open fun dispose(cause: Throwable? = null) {
supervisor.cancel(CancellationException("handler closed", cause)) if (supervisor.isActive) {
if (cause != null) {
supervisor.cancel(CancellationException("handler closed", cause))
} else {
supervisor.cancel()
}
}
} }
/*
@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
...@@ -24,7 +24,10 @@ expect val deviceName: String ...@@ -24,7 +24,10 @@ expect val deviceName: String
*/ */
expect fun crc32(key: ByteArray): Int expect fun crc32(key: ByteArray): Int
expect fun ByteArray.unzip(): ByteArray /**
* 解 zip 压缩
*/
expect fun ByteArray.unzip(offset: Int = 0, length: Int = this.size - offset): ByteArray
/** /**
* MD5 算法 * MD5 算法
...@@ -48,4 +51,10 @@ expect fun localIpAddress(): String ...@@ -48,4 +51,10 @@ expect fun localIpAddress(): String
*/ */
expect val Http: HttpClient expect val Http: HttpClient
expect fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher expect fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher
\ No newline at end of file
internal fun ByteArray.checkOffsetAndLength(offset: Int, length: Int){
require(offset >= 0) { "offset shouldn't be negative: $offset" }
require(length >= 0) { "length shouldn't be negative: $length" }
require(offset + length <= this.size) { "offset ($offset) + length ($length) > array.size (${this.size})" }
}
\ No newline at end of file
...@@ -15,23 +15,46 @@ expect class ECDHKeyPair { ...@@ -15,23 +15,46 @@ expect class ECDHKeyPair {
val privateKey: ECDHPrivateKey val privateKey: ECDHPrivateKey
val publicKey: ECDHPublicKey val publicKey: ECDHPublicKey
val shareKey: ByteArray /**
* 私匙和固定公匙([initialPublicKey]) 计算得到的 shareKey
*/
val initialShareKey: ByteArray
} }
/**
* 椭圆曲线密码, ECDH 加密
*/
expect class ECDH(keyPair: ECDHKeyPair) { expect class ECDH(keyPair: ECDHKeyPair) {
val keyPair: ECDHKeyPair val keyPair: ECDHKeyPair
/**
* 由 [keyPair] 的私匙和 [peerPublicKey] 计算 shareKey
*/
fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray
companion object { companion object {
/**
* 由完整的 publicKey ByteArray 得到 [ECDHPublicKey]
*/
fun constructPublicKey(key: ByteArray): ECDHPublicKey fun constructPublicKey(key: ByteArray): ECDHPublicKey
/**
* 生成随机密匙对
*/
fun generateKeyPair(): ECDHKeyPair fun generateKeyPair(): ECDHKeyPair
/**
* 由一对密匙计算 shareKey
*/
fun calculateShareKey(privateKey: ECDHPrivateKey, publicKey: ECDHPublicKey): ByteArray fun calculateShareKey(privateKey: ECDHPrivateKey, publicKey: ECDHPublicKey): ByteArray
} }
override fun toString(): String override fun toString(): String
} }
/**
*
*/
@Suppress("FunctionName") @Suppress("FunctionName")
expect fun ECDH(): ECDH expect fun ECDH(): ECDH
......
...@@ -38,6 +38,14 @@ fun ByteReadPacket.transferTo(outputStream: OutputStream) { ...@@ -38,6 +38,14 @@ fun ByteReadPacket.transferTo(outputStream: OutputStream) {
} }
} }
fun <R> ByteReadPacket.useBytes(
n: Int = remaining.toInt(),//not that safe but adequate
block: (data: ByteArray, length: Int) -> R
): R = ByteArrayPool.useInstance {
this.readFully(it, 0, n)
block(it, n)
}
fun ByteReadPacket.readRemainingBytes( fun ByteReadPacket.readRemainingBytes(
n: Int = remaining.toInt()//not that safe but adequate n: Int = remaining.toInt()//not that safe but adequate
): ByteArray = ByteArray(n).also { readAvailable(it, 0, n) } ): ByteArray = ByteArray(n).also { readAvailable(it, 0, n) }
...@@ -75,10 +83,21 @@ fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort(). ...@@ -75,10 +83,21 @@ fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().
private inline fun <R> inline(block: () -> R): R = block() private inline fun <R> inline(block: () -> R): R = block()
fun Input.readTLVMap(tagSize: Int = 2): MutableMap<Int, ByteArray> = readTLVMap(true, tagSize)
typealias TlvMap = MutableMap<Int, ByteArray>
fun TlvMap.getOrFail(tag: Int): ByteArray {
return this[tag] ?: error("cannot find tlv 0x${tag.toUHexString("")}($tag)")
}
inline fun TlvMap.getOrFail(tag: Int, lazyMessage: (tag: Int) -> String): ByteArray {
return this[tag] ?: error(lazyMessage(tag))
}
fun Input.readTLVMap(tagSize: Int = 2): TlvMap = readTLVMap(true, tagSize)
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode")
fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int): MutableMap<Int, ByteArray> { fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int): TlvMap {
val map = mutableMapOf<Int, ByteArray>() val map = mutableMapOf<Int, ByteArray>()
var key = 0 var key = 0
......
...@@ -7,8 +7,10 @@ import io.ktor.client.engine.cio.CIO ...@@ -7,8 +7,10 @@ import io.ktor.client.engine.cio.CIO
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.io.core.copyTo import kotlinx.io.core.copyTo
import kotlinx.io.pool.useInstance
import kotlinx.io.streams.asInput import kotlinx.io.streams.asInput
import kotlinx.io.streams.asOutput import kotlinx.io.streams.asOutput
import net.mamoe.mirai.utils.io.ByteArrayPool
import java.io.* import java.io.*
import java.net.InetAddress import java.net.InetAddress
import java.security.MessageDigest import java.security.MessageDigest
...@@ -56,18 +58,23 @@ actual fun localIpAddress(): String = InetAddress.getLocalHost().hostAddress ...@@ -56,18 +58,23 @@ actual fun localIpAddress(): String = InetAddress.getLocalHost().hostAddress
actual val Http: HttpClient get() = HttpClient(CIO) actual val Http: HttpClient get() = HttpClient(CIO)
actual fun ByteArray.unzip(): ByteArray { actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray {
this.checkOffsetAndLength(offset, length)
if (length == 0) return ByteArray(0)
val inflater = Inflater() val inflater = Inflater()
inflater.reset() inflater.reset()
val input = this ByteArrayOutputStream().use { output ->
val output = ByteArrayOutputStream() inflater.setInput(this, offset, length)
inflater.setInput(input) ByteArrayPool.useInstance {
val buffer = ByteArray(128) while (!inflater.finished()) {
while (!inflater.finished()) { output.write(it, 0, inflater.inflate(it))
output.write(buffer, 0, inflater.inflate(buffer)) }
}
inflater.end()
return output.toByteArray()
} }
inflater.end()
return output.toByteArray()
} }
actual fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher { actual fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher {
......
...@@ -17,7 +17,7 @@ actual class ECDHKeyPair( ...@@ -17,7 +17,7 @@ actual class ECDHKeyPair(
actual val privateKey: ECDHPrivateKey get() = delegate.private actual val privateKey: ECDHPrivateKey get() = delegate.private
actual val publicKey: ECDHPublicKey get() = delegate.public actual val publicKey: ECDHPublicKey get() = delegate.public
actual val shareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey) actual val initialShareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey)
} }
@Suppress("FunctionName") @Suppress("FunctionName")
......
...@@ -36,7 +36,11 @@ actual class PlatformSocket : Closeable { ...@@ -36,7 +36,11 @@ actual class PlatformSocket : Closeable {
* @throws SendPacketInternalException * @throws SendPacketInternalException
*/ */
actual suspend inline fun send(packet: ByteReadPacket) { actual suspend inline fun send(packet: ByteReadPacket) {
writeChannel.writePacket(packet) try {
writeChannel.writePacket(packet)
} catch (e: Exception) {
throw SendPacketInternalException(e)
}
} }
/** /**
...@@ -45,7 +49,11 @@ actual class PlatformSocket : Closeable { ...@@ -45,7 +49,11 @@ actual class PlatformSocket : Closeable {
actual suspend inline fun read(): ByteReadPacket { actual suspend inline fun read(): ByteReadPacket {
// do not use readChannel.readRemaining() !!! this function never returns // do not use readChannel.readRemaining() !!! this function never returns
ByteArrayPool.useInstance { buffer -> ByteArrayPool.useInstance { buffer ->
val count = readChannel.readAvailable(buffer) val count = try {
readChannel.readAvailable(buffer)
} catch (e: Exception) {
throw ReadPacketInternalException(e)
}
return buffer.toReadPacket(0, count) return buffer.toReadPacket(0, count)
} }
} }
......
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