mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-10 18:40:15 +08:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
dcb8b0ac3d
25
README.md
25
README.md
@ -28,8 +28,30 @@
|
|||||||
|
|
||||||
您的 star 是对我们最大的鼓励(点击项目右上角);
|
您的 star 是对我们最大的鼓励(点击项目右上角);
|
||||||
|
|
||||||
|
## Features
|
||||||
|
#### mirai-core
|
||||||
|
通用 API 模块,请参考此模块调用 Mirai.
|
||||||
|
#### mirai-core-timpc
|
||||||
|
TIM PC (2.3.2 版本,2019 年 8 月)协议的实现,相较于 core,仅新增少量 API. 详见 [README.md](mirai-core-timpc/)
|
||||||
|
支持的功能:
|
||||||
|
- 消息收发:图片文字复合消息,图片消息
|
||||||
|
- 群管功能:群员列表,禁言
|
||||||
|
|
||||||
|
(目前不再更新,请关注安卓协议)
|
||||||
|
|
||||||
|
#### mirai-core-qqandroid
|
||||||
|
QQ for Android (8.2.0 版本,2019 年 12 月)协议的实现,目前还未完成。
|
||||||
|
- 高兼容性:Mirai 协议仅含极少部分为硬编码,其余全部随官方方式动态生成
|
||||||
|
- 高安全性:密匙随机,ECDH 动态计算,硬件信息真机模拟(Android 平台获取真机信息)
|
||||||
|
|
||||||
|
开发进度:
|
||||||
|
- 完成 密码登录 (2020/1/23)
|
||||||
|
- 进行中 验证码登录
|
||||||
|
- 进行中 消息解析
|
||||||
|
- 进行中 图片上传下载
|
||||||
|
|
||||||
## 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
|
||||||
@ -48,6 +70,7 @@ repositories{
|
|||||||
**Mirai 目前还处于实验性阶段, 我们无法保证任何稳定性, API 也可能会随时修改.**
|
**Mirai 目前还处于实验性阶段, 我们无法保证任何稳定性, API 也可能会随时修改.**
|
||||||
|
|
||||||
现在 Mirai 只支持 TIM PC 协议. QQ Android 协议正在开发中.
|
现在 Mirai 只支持 TIM PC 协议. QQ Android 协议正在开发中.
|
||||||
|
|
||||||
**common**
|
**common**
|
||||||
```kotlin
|
```kotlin
|
||||||
implementation("net.mamoe:mirai-core-timpc-common:VERSION")
|
implementation("net.mamoe:mirai-core-timpc-common:VERSION")
|
||||||
|
@ -4,6 +4,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
google()
|
google()
|
||||||
|
maven { url 'https://dl.bintray.com/kotlin/kotlin-dev/'}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -11,8 +11,7 @@ import net.mamoe.mirai.qqandroid.event.PacketReceivedEvent
|
|||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.KnownPacketFactories
|
import net.mamoe.mirai.qqandroid.network.protocol.packet.KnownPacketFactories
|
||||||
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.login.LoginPacket
|
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket.LoginPacketResponse.Captcha
|
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket.LoginPacketResponse.*
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket.LoginPacketResponse.Success
|
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.SvcReqRegisterPacket
|
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.SvcReqRegisterPacket
|
||||||
import net.mamoe.mirai.utils.*
|
import net.mamoe.mirai.utils.*
|
||||||
import net.mamoe.mirai.utils.io.*
|
import net.mamoe.mirai.utils.io.*
|
||||||
@ -31,7 +30,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
|||||||
launch(CoroutineName("Incoming Packet Receiver")) { processReceive() }
|
launch(CoroutineName("Incoming Packet Receiver")) { processReceive() }
|
||||||
|
|
||||||
bot.logger.info("Trying login")
|
bot.logger.info("Trying login")
|
||||||
when (val response = LoginPacket.SubCommand9(bot.client).sendAndExpect<LoginPacket.LoginPacketResponse>()) {
|
when (val response: LoginPacket.LoginPacketResponse = LoginPacket.SubCommand9(bot.client).sendAndExpect()) {
|
||||||
is Captcha -> when (response) {
|
is Captcha -> when (response) {
|
||||||
is Captcha.Picture -> {
|
is Captcha.Picture -> {
|
||||||
bot.logger.info("需要图片验证码")
|
bot.logger.info("需要图片验证码")
|
||||||
@ -41,6 +40,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is Error -> error(response.toString())
|
||||||
|
|
||||||
is Success -> {
|
is Success -> {
|
||||||
bot.logger.info("Login successful")
|
bot.logger.info("Login successful")
|
||||||
}
|
}
|
||||||
@ -207,10 +208,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
|||||||
suspend fun <E : Packet> OutgoingPacket.sendAndExpect(): E {
|
suspend fun <E : Packet> OutgoingPacket.sendAndExpect(): E {
|
||||||
val handler = PacketListener(commandName = commandName, sequenceId = sequenceId)
|
val handler = PacketListener(commandName = commandName, sequenceId = sequenceId)
|
||||||
packetListeners.addLast(handler)
|
packetListeners.addLast(handler)
|
||||||
//println(delegate.readBytes().toUHexString())
|
channel.send(delegate)
|
||||||
println("Sending length=" + delegate.remaining)
|
|
||||||
channel.send(delegate)//) { packetListeners.remove(handler); "Cannot send packet" }
|
|
||||||
println("Packet sent")
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
return handler.await() as E
|
return handler.await() as E
|
||||||
}
|
}
|
||||||
|
@ -172,6 +172,12 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
|
|||||||
|
|
||||||
sealed class LoginPacketResponse : Packet {
|
sealed class LoginPacketResponse : Packet {
|
||||||
object Success : LoginPacketResponse()
|
object Success : LoginPacketResponse()
|
||||||
|
data class Error(
|
||||||
|
val title: String,
|
||||||
|
val message: String,
|
||||||
|
val errorInfo: String
|
||||||
|
) : LoginPacketResponse()
|
||||||
|
|
||||||
sealed class Captcha : LoginPacketResponse() {
|
sealed class Captcha : LoginPacketResponse() {
|
||||||
class Slider(
|
class Slider(
|
||||||
val data: IoBuffer
|
val data: IoBuffer
|
||||||
@ -197,52 +203,56 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
|
|||||||
println("subCommand=$subCommand")
|
println("subCommand=$subCommand")
|
||||||
val type = readByte()
|
val type = readByte()
|
||||||
println("type=$type")
|
println("type=$type")
|
||||||
when (type.toInt()) {
|
|
||||||
0 -> {
|
discardExact(2)
|
||||||
onLoginSuccess(bot)
|
val tlvMap: Map<Int, ByteArray> = this.readTLVMap()
|
||||||
}
|
return when (type.toInt()) {
|
||||||
1 -> {
|
0 -> onLoginSuccess(tlvMap, bot)
|
||||||
throw Exception("Wrong Password")
|
1, 15 -> onErrorMessage(tlvMap)
|
||||||
}
|
2 -> onSolveLoginCaptcha(tlvMap, bot)
|
||||||
2 -> {
|
else -> error("unknown login result type: $type")
|
||||||
onSolveLoginCaptcha(bot)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (type.toInt() != 0) {
|
}
|
||||||
DebugLogger.debug("unknown login result type: $type")
|
|
||||||
}
|
private fun onErrorMessage(tlvMap: Map<Int, ByteArray>): LoginPacketResponse.Error {
|
||||||
return LoginPacketResponse.Success
|
return tlvMap[0x146]?.toReadPacket()?.run {
|
||||||
|
readShort() // ver
|
||||||
|
readShort() // code
|
||||||
|
|
||||||
|
val title = readUShortLVString()
|
||||||
|
val message = readUShortLVString()
|
||||||
|
val errorInfo = readUShortLVString()
|
||||||
|
|
||||||
|
LoginPacketResponse.Error(title, message, errorInfo)
|
||||||
|
} ?: error("Cannot find error message")
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseExperimental(MiraiDebugAPI::class)
|
@UseExperimental(MiraiDebugAPI::class)
|
||||||
suspend fun ByteReadPacket.onSolveLoginCaptcha(bot: QQAndroidBot) = this.debugPrint("login验证码解析").run {
|
private suspend fun onSolveLoginCaptcha(tlvMap: Map<Int, ByteArray>, bot: QQAndroidBot): LoginPacketResponse.Captcha {
|
||||||
val client = bot.client
|
val client = bot.client
|
||||||
debugDiscardExact(2)
|
// val ret = tlvMap[0x104]?.let { println(it.toUHexString()) }
|
||||||
val tlvMap: Map<Int, ByteArray> = this.readTLVMap()
|
|
||||||
// val ret = tlvMap[0x104]?.let { println(it.toUHexString()) }
|
|
||||||
println()
|
println()
|
||||||
val question = tlvMap[0x165] ?: error("CAPTCHA QUESTION UNKNOWN")
|
val question = tlvMap[0x165] ?: error("CAPTCHA QUESTION UNKNOWN")
|
||||||
when(question[18].toUHexString()){
|
when (question[18].toUHexString()) {
|
||||||
"36" -> {
|
"36" -> {
|
||||||
//图片验证
|
//图片验证
|
||||||
debugPrint("是一个图片验证码")
|
DebugLogger.debug("是一个图片验证码")
|
||||||
val imageData = tlvMap[0x165]
|
val imageData = tlvMap[0x165]
|
||||||
bot.configuration.captchaSolver.invoke(
|
bot.configuration.captchaSolver.invoke(
|
||||||
bot,
|
bot,
|
||||||
(tlvMap[0x165] ?: error("Captcha Image Data Not Found")).toIoBuffer()
|
(tlvMap[0x105] ?: error("Captcha Image Data Not Found")).toIoBuffer()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
error("UNKNOWN CAPTCHA QUESTION: $question")
|
error("UNKNOWN CAPTCHA QUESTION: $question")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return TODO()
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseExperimental(MiraiDebugAPI::class)
|
@UseExperimental(MiraiDebugAPI::class)
|
||||||
fun ByteReadPacket.onLoginSuccess(bot: QQAndroidBot) = this.debugPrint("login成功解析").run {
|
private fun onLoginSuccess(tlvMap: Map<Int, ByteArray>, bot: QQAndroidBot): LoginPacketResponse.Success {
|
||||||
val client = bot.client
|
val client = bot.client
|
||||||
debugDiscardExact(2)
|
|
||||||
val tlvMap: Map<Int, ByteArray> = this.readTLVMap()
|
|
||||||
println("TLV KEYS: " + tlvMap.keys.joinToString { it.contentToString() })
|
println("TLV KEYS: " + tlvMap.keys.joinToString { it.contentToString() })
|
||||||
|
|
||||||
tlvMap[0x150]?.let { client.analysisTlv150(it) }
|
tlvMap[0x150]?.let { client.analysisTlv150(it) }
|
||||||
@ -446,6 +456,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return LoginPacketResponse.Success
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
|||||||
@UseExperimental(MiraiExperimentalAPI::class)
|
@UseExperimental(MiraiExperimentalAPI::class)
|
||||||
final override val uin: Long
|
final override val uin: Long
|
||||||
get() = account.id
|
get() = account.id
|
||||||
final override val logger: MiraiLogger = configuration.logger ?: DefaultLogger("Bot($uin)")
|
final override val logger: MiraiLogger = configuration.logger ?: DefaultLogger("Bot($uin)").also { configuration.logger = it }
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@Suppress("LeakingThis")
|
@Suppress("LeakingThis")
|
||||||
|
@ -4,7 +4,6 @@ import kotlinx.io.core.IoBuffer
|
|||||||
import net.mamoe.mirai.Bot
|
import net.mamoe.mirai.Bot
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
import kotlin.coroutines.coroutineContext
|
|
||||||
import kotlin.jvm.JvmStatic
|
import kotlin.jvm.JvmStatic
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,7 +25,7 @@ class BotConfiguration {
|
|||||||
/**
|
/**
|
||||||
* 日志记录器
|
* 日志记录器
|
||||||
*/
|
*/
|
||||||
var logger: PlatformLogger? = null
|
var logger: MiraiLogger? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 父 [CoroutineContext]
|
* 父 [CoroutineContext]
|
||||||
|
Loading…
Reference in New Issue
Block a user