Merge remote-tracking branch 'origin/master'

# Conflicts:
#	mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
This commit is contained in:
jiahua.liu 2020-02-01 20:19:13 +08:00
commit bd6388297d
16 changed files with 129 additions and 108 deletions

View File

@ -5,6 +5,7 @@ import android.net.wifi.WifiManager
import android.os.Build
import android.telephony.TelephonyManager
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.utils.Context
import net.mamoe.mirai.utils.localIpAddress
import net.mamoe.mirai.utils.md5
import java.io.File

View File

@ -3,29 +3,29 @@ package net.mamoe.mirai.qqandroid.network
import kotlinx.atomicfu.AtomicRef
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input
import kotlinx.io.core.buildPacket
import kotlinx.io.core.use
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.data.MultiPacket
import net.mamoe.mirai.data.Packet
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.*
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.QQImpl
import net.mamoe.mirai.qqandroid.event.ForceOfflineEvent
import net.mamoe.mirai.qqandroid.event.PacketReceivedEvent
import net.mamoe.mirai.qqandroid.network.protocol.packet.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.cryptor.contentToString
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.getValue
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.unsafeWeakRef
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
@ -41,14 +41,13 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
private lateinit var channel: PlatformSocket
override suspend fun login() {
if (::channel.isInitialized) {
channel.close()
}
channel = PlatformSocket()
channel.connect("113.96.13.208", 8080)
launch(CoroutineName("Incoming Packet Receiver")) { processReceive() }
this.launch(CoroutineName("Incoming Packet Receiver")) { processReceive() }
// bot.logger.info("Trying login")
var response: LoginPacket.LoginPacketResponse = LoginPacket.SubCommand9(bot.client).sendAndExpect()
@ -104,10 +103,18 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
}
override suspend fun init() {
//start updating friend/group list
bot.logger.info("Start updating friend/group list")
bot.logger.info("开始加载好友信息")
this@QQAndroidBotNetworkHandler.subscribeAlways<ForceOfflineEvent> {
if (this@QQAndroidBotNetworkHandler.bot == this.bot) {
close()
}
}
/*
* 开始加载Contact表
* */
var currentFriendCount = 0
var totalFriendCount: Short = 0
var totalFriendCount: Short
while (true) {
val data = FriendList.GetFriendGroupList(
bot.client,
@ -117,14 +124,13 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
0
).sendAndExpect<FriendList.GetFriendGroupList.Response>()
totalFriendCount = data.totalFriendCount
bot.qqs.delegate.addAll(
data.friendList.map {
QQImpl(this@QQAndroidBotNetworkHandler.bot, EmptyCoroutineContext, it.friendUin!!).also {
currentFriendCount++
}
}
)
bot.logger.info("正在加载好友信息 ${currentFriendCount}/${totalFriendCount}")
data.friendList.forEach {
// atomic add
bot.qqs.delegate.addLast(QQImpl(bot, EmptyCoroutineContext, it.friendUin).also {
currentFriendCount++
})
}
bot.logger.verbose("正在加载好友信息 ${currentFriendCount}/${totalFriendCount}")
if (currentFriendCount >= totalFriendCount) {
break
}
@ -138,22 +144,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
).sendAndExpect<FriendList.GetTroopList.Response>(100000)
println(data.contentToString())
*/
}
/**
* 单线程处理包的接收, 分割和连接.
*/
@Suppress("PrivatePropertyName")
private val PacketReceiveDispatcher = newCoroutineDispatcher(1)
/**
* 单线程处理包的解析 (协程挂起效率够)
*/
@Suppress("PrivatePropertyName")
private val PacketProcessDispatcher = newCoroutineDispatcher(1)
/**
* 缓存超时处理的 [Job]. 超时后将清空缓存, 以免阻碍后续包的处理
*/
@ -168,12 +160,12 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
private var expectingRemainingLength: Long = 0
/**
* [PacketProcessDispatcher] 调度器中解析包内容.
* 解析包内容.
*
* @param input 一个完整的包的内容, 去掉开头的 int 包长度
*/
fun parsePacketAsync(input: Input): Job {
return this.launch(PacketProcessDispatcher) {
return this.launch {
input.use { parsePacket(it) }
}
}
@ -320,28 +312,29 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
val rawInput = try {
channel.read()
} catch (e: ClosedChannelException) {
dispose()
bot.tryReinitializeNetworkHandler(e)
return
} catch (e: ReadPacketInternalException) {
bot.logger.error("Socket channel read failed: ${e.message}")
dispose()
bot.tryReinitializeNetworkHandler(e)
return
} catch (e: CancellationException) {
return
} catch (e: Throwable) {
bot.logger.error("Caught unexpected exceptions", e)
dispose()
bot.tryReinitializeNetworkHandler(e)
return
}
launch(context = PacketReceiveDispatcher + CoroutineName("Incoming Packet handler"), start = CoroutineStart.ATOMIC) {
processPacket(rawInput)
launch(CoroutineName("Incoming Packet handler"), start = CoroutineStart.ATOMIC) {
packetReceiveLock.withLock {
processPacket(rawInput)
}
}
}
}
private val packetReceiveLock: Mutex = Mutex()
/**
* 发送一个包, 并挂起直到接收到指定的返回包或超时(3000ms)
*/
@ -364,7 +357,9 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
}
private suspend inline fun <E : Packet> OutgoingPacket.doSendAndReceive(timeoutMillis: Long = 3000, handler: PacketListener): E {
channel.send(delegate)
withContext(this@QQAndroidBotNetworkHandler.coroutineContext + CoroutineName("Packet sender")) {
channel.send(delegate)
}
bot.logger.info("Send: ${this.commandName}")
return withTimeoutOrNull(timeoutMillis) {
@Suppress("UNCHECKED_CAST")
@ -386,11 +381,11 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
fun filter(commandName: String, sequenceId: Int) = this.commandName == commandName && this.sequenceId == sequenceId
}
override fun dispose(cause: Throwable?) {
override fun close(cause: Throwable?) {
if (::channel.isInitialized) {
channel.close()
}
super.dispose(cause)
super.close(cause)
}
override suspend fun awaitDisconnection() = supervisor.join()

View File

@ -67,7 +67,7 @@ internal class FriendListSubSrvRspCode(
@Serializable
internal class FriendInfo(
@SerialId(0) val friendUin: Long? = 0,
@SerialId(0) val friendUin: Long,
@SerialId(1) val groupId: Byte,
@SerialId(2) val faceId: Short,
@SerialId(3) val remark: String = "",

View File

@ -11,6 +11,6 @@ class SyncCookie(
@SerialId(4) val unknown2: Long = 3497826378,
@SerialId(5) val const1: Long = 1680172298,
@SerialId(6) val const2: Long = 2424173273,
@SerialId(7) val unknown3: Long = 83,
@SerialId(7) val unknown3: Long = 0,
@SerialId(8) val unknown4: Long = 0
) : ProtoBuf

View File

@ -16,7 +16,6 @@ import net.mamoe.mirai.utils.io.writeQQ
* 待发送给服务器的数据包. 它代表着一个 [ByteReadPacket].
* 只有最终的包才会被包装为 [OutgoingPacket].
*/
@UseExperimental(ExperimentalUnsignedTypes::class)
internal class OutgoingPacket constructor(
name: String?,
val commandName: String,

View File

@ -25,8 +25,8 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.qqandroid.utils.toMessageChain
import net.mamoe.mirai.qqandroid.utils.toRichTextElems
import net.mamoe.mirai.utils.cryptor.contentToString
import net.mamoe.mirai.utils.currentTimeSeconds
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.toReadPacket
import kotlin.math.absoluteValue
import kotlin.random.Random
@ -61,8 +61,8 @@ internal class MessageSvc {
client: QQAndroidClient,
msgTime: Long //PbPushMsg.msg.msgHead.msgTime
): OutgoingPacket = buildOutgoingUniPacket(
client,
extraData = EXTRA_DATA.toReadPacket()
client//,
// extraData = EXTRA_DATA.toReadPacket()
) {
writeProtoBuf(
MsgSvc.PbGetMsgReq.serializer(),
@ -163,8 +163,9 @@ internal class MessageSvc {
)
),
msgSeq = client.atomicNextMessageSequenceId(),
msgRand = Random.nextInt().absoluteValue
// syncCookie = client.c2cMessageSync.syncCookie.takeIf { it.isNotEmpty() } ?: "08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00".hexToBytes(),
msgRand = Random.nextInt().absoluteValue,
syncCookie = client.c2cMessageSync.syncCookie?.takeIf { it.isNotEmpty() }
?: SyncCookie(currentTimeSeconds).toByteArray(SyncCookie.serializer())
// msgVia = 1
)
)

View File

@ -24,4 +24,10 @@ object TIMPC : BotFactory {
* 使用指定的 [配置][configuration] 构造 [Bot] 实例
*/
fun Bot(qq: Long, password: String, configuration: BotConfiguration = BotConfiguration.Default): Bot = TIMPCBot(BotAccount(qq, password), configuration)
}
}
/**
* 使用指定的 [配置][configuration] 构造 [Bot] 实例
*/
inline fun TIMPC.Bot(qq: Long, password: String, configuration: (BotConfiguration.() -> Unit)): Bot =
this.Bot(qq, password, BotConfiguration().apply(configuration))

View File

@ -91,8 +91,8 @@ internal class TIMPCBotNetworkHandler internal constructor(coroutineContext: Cor
heartbeatJob?.join()
}
override fun dispose(cause: Throwable?) {
super.dispose(cause)
override fun close(cause: Throwable?) {
super.close(cause)
this.heartbeatJob?.cancel(CancellationException("handler closed"))
this.heartbeatJob = null
@ -312,6 +312,7 @@ internal class TIMPCBotNetworkHandler internal constructor(coroutineContext: Cor
else -> error("No decrypter is found")
} as? D ?: error("Internal error: could not cast decrypter which is found for factory to class Decrypter")
@UseExperimental(MiraiInternalAPI::class)
suspend fun onPacketReceived(packet: Any) {//complex function, but it doesn't matter
when (packet) {
is TouchPacket.TouchResponse.OK -> {

View File

@ -22,6 +22,7 @@ data class BotAccount(
/**
* 标记直接访问 [BotAccount.id], 而不是访问 [Bot.uin]. 这可能会不兼容未来的 API 修改.
*/
@MiraiInternalAPI
@Retention(AnnotationRetention.SOURCE)
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
@Experimental(level = Experimental.Level.WARNING)

View File

@ -6,6 +6,7 @@ import kotlinx.coroutines.*
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.closeAndJoin
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.logStacktrace
import kotlin.coroutines.CoroutineContext
@ -92,35 +93,39 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
try {
if (::_network.isInitialized) {
BotOfflineEvent(this).broadcast()
_network.dispose(cause)
_network.closeAndJoin(cause)
}
} catch (e: Exception) {
logger.error("Cannot close network handler", e)
}
_network = createNetworkHandler(this.coroutineContext)
loginLoop@ while (true) {
_network = createNetworkHandler(this.coroutineContext)
try {
_network.login()
break@loginLoop
} catch (e: Exception) {
e.logStacktrace()
_network.dispose(e)
_network.closeAndJoin(e)
}
logger.warning("Login failed. Retrying in 3s...")
delay(3000)
}
while (true) {
try {
return _network.init()
} catch (e: Exception) {
e.logStacktrace()
_network.dispose(e)
repeat(1) block@{
repeat(2) {
try {
_network.init()
return@block
} catch (e: Exception) {
e.logStacktrace()
}
logger.warning("Init failed. Retrying in 3s...")
delay(3000)
}
logger.warning("Init failed. Retrying in 3s...")
delay(3000)
logger.error("cannot init. some features may be affected")
}
}
protected abstract fun createNetworkHandler(coroutineContext: CoroutineContext): N
@ -130,12 +135,12 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
@UseExperimental(MiraiInternalAPI::class)
override fun dispose(throwable: Throwable?) {
if (throwable == null) {
network.dispose()
network.close()
this.botJob.complete()
groups.delegate.clear()
qqs.delegate.clear()
} else {
network.dispose(throwable)
network.close(throwable)
this.botJob.completeExceptionally(throwable)
groups.delegate.clear()
qqs.delegate.clear()

View File

@ -24,7 +24,7 @@ import net.mamoe.mirai.utils.io.PlatformDatagramChannel
* - Key 刷新
* - 所有数据包处理和发送
*
* [BotNetworkHandler.dispose] 时将会 [取消][Job.cancel] 所有此作用域下的协程
* [BotNetworkHandler.close] 时将会 [取消][Job.cancel] 所有此作用域下的协程
*/
@Suppress("PropertyName")
abstract class BotNetworkHandler : CoroutineScope {
@ -49,6 +49,8 @@ abstract class BotNetworkHandler : CoroutineScope {
/**
* 初始化获取好友列表等值.
*
* 不要使用这个 API. 它会在登录完成后被自动调用.
*/
@MiraiInternalAPI
open suspend fun init() {
@ -62,13 +64,18 @@ abstract class BotNetworkHandler : CoroutineScope {
/**
* 关闭网络接口, 停止所有有关协程和任务
*/
open fun dispose(cause: Throwable? = null) {
open fun close(cause: Throwable? = null) {
if (supervisor.isActive) {
if (cause != null) {
supervisor.cancel(CancellationException("handler closed", cause))
supervisor.cancel(CancellationException("NetworkHandler closed", cause))
} else {
supervisor.cancel()
supervisor.cancel(CancellationException("NetworkHandler closed"))
}
}
}
}
suspend fun BotNetworkHandler.closeAndJoin(cause: Throwable? = null){
this.close(cause)
this.supervisor.join()
}

View File

@ -4,6 +4,7 @@ package net.mamoe.mirai
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.Context
import net.mamoe.mirai.utils.ContextImpl
// Do not use ServiceLoader. Probably not working on MPP
@PublishedApi
@ -20,7 +21,14 @@ internal val factory: BotFactory = run {
No BotFactory found. Please ensure that you've added dependency of protocol modules.
Available modules:
- net.mamoe:mirai-core-timpc
- net.mamoe:mirai-core-qqandroid (recommended)
You should have at lease one protocol module installed.
-------------------------------------------------------
找不到 BotFactory. 请确保你依赖了至少一个协议模块.
可用的协议模块:
- net.mamoe:mirai-core-timpc
- net.mamoe:mirai-core-qqandroid (推荐)
请添加上述任一模块的依赖( mirai-core 版本相同)
""".trimIndent()
)
@ -34,4 +42,17 @@ fun Bot(context: Context, qq: Long, password: String, configuration: BotConfigur
* 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例
*/
inline fun Bot(context: Context, qq: Long, password: String, configuration: (BotConfiguration.() -> Unit)): Bot =
factory.Bot(context, qq, password, configuration)
factory.Bot(context, qq, password, configuration)
/**
* 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例
*/
fun Bot(qq: Long, password: String, configuration: BotConfiguration = BotConfiguration.Default): Bot =
factory.Bot(ContextImpl(), qq, password, configuration)
/**
* 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例
*/
inline fun Bot(qq: Long, password: String, configuration: (BotConfiguration.() -> Unit)): Bot =
factory.Bot(ContextImpl(), qq, password, configuration)

View File

@ -17,6 +17,7 @@ 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.Bot
import net.mamoe.mirai.timpc.TIMPC
import net.mamoe.mirai.utils.suspendToExternalImage
import java.io.File
@ -39,10 +40,8 @@ private fun readTestAccount(): BotAccount? {
@Suppress("UNUSED_VARIABLE")
suspend fun main() {
val bot = TIMPC.Bot( // JVM 下也可以不写 `TIMPC.` 引用顶层函数
readTestAccount() ?: BotAccount(//填写你的账号
id = 1994701121,
passwordPlainText = "123456"
)
1994701121,
"123456"
) {
// 覆盖默认的配置
randomDeviceName = false
@ -134,6 +133,7 @@ fun Bot.messageDSL() {
reply(message)
}
listener.complete() // 停止监听
// 自定义的 filter, filter 中 it 为转为 String 的消息.
// 也可以用任何能在处理时使用的变量, 如 subject, sender, message

View File

@ -14,6 +14,7 @@ import kotlinx.io.core.IoBuffer
import kotlinx.io.core.readBytes
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.timpc.Bot
import net.mamoe.mirai.timpc.TIMPC
import net.mamoe.mirai.utils.LoginFailedException
import net.mamoe.mirai.utils.LoginSolver

View File

@ -8,7 +8,6 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.mamoe.mirai.Bot
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.event.Subscribable
@ -23,32 +22,18 @@ 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.utils.ContextImpl
import java.io.File
import java.util.*
import javax.swing.filechooser.FileSystemView
import kotlin.random.Random
private fun readTestAccount(): BotAccount? {
val file = File("testAccount.txt")
if (!file.exists() || !file.canRead()) {
return null
}
val lines = file.readLines()
return try {
BotAccount(lines[0].toLong(), lines[1])
} catch (e: Throwable) {
null
}
}
@Suppress("UNUSED_VARIABLE")
suspend fun main() {
val bot = Bot(
readTestAccount() ?: BotAccount(
id = 913366033,
passwordPlainText = "a18260132383"
)
ContextImpl(),
913366033,
"a18260132383"
) {
// override config here.
}.alsoLogin()