diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt index e218225b1..3d26048fd 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt @@ -26,6 +26,9 @@ internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: Coroutin TODO("not implemented") } + override val isOnline: Boolean + get() = true + override suspend fun queryProfile(): Profile { TODO("not implemented") } @@ -40,11 +43,16 @@ internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: Coroutin } -internal class MemberImpl(bot: QQAndroidBot, group: Group, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), Member { - override val group: Group by group.unsafeWeakRef() +internal class MemberImpl( + qq: QQImpl, + group: GroupImpl, + override val coroutineContext: CoroutineContext +) : ContactImpl(), Member, QQ by qq { + override val group: GroupImpl by group.unsafeWeakRef() + val qq: QQImpl by qq.unsafeWeakRef() + override val permission: MemberPermission get() = TODO("not implemented") - override val bot: QQAndroidBot by bot.unsafeWeakRef() override suspend fun mute(durationSeconds: Int): Boolean { TODO("not implemented") @@ -54,26 +62,6 @@ internal class MemberImpl(bot: QQAndroidBot, group: Group, override val coroutin TODO("not implemented") } - override suspend fun queryProfile(): Profile { - TODO("not implemented") - } - - override suspend fun queryPreviousNameList(): PreviousNameList { - TODO("not implemented") - } - - override suspend fun queryRemark(): FriendNameRemark { - TODO("not implemented") - } - - override suspend fun sendMessage(message: MessageChain) { - TODO("not implemented") - } - - override suspend fun uploadImage(image: ExternalImage): ImageId { - TODO("not implemented") - } - } @@ -89,7 +77,7 @@ internal class GroupImpl(bot: QQAndroidBot, override val coroutineContext: Corou override val members: ContactList<Member> = ContactList(LockFreeLinkedList()) override fun getMember(id: Long): Member = - members.delegate.filteringGetOrAdd({ it.id == id }, { MemberImpl(bot as QQAndroidBot, this, coroutineContext, id) }) + members.delegate.filteringGetOrAdd({ it.id == id }, { MemberImpl(bot.getQQ(id) as QQImpl, this, coroutineContext) }) override suspend fun updateGroupInfo(): GroupInfo { TODO("not implemented") diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt index f2d8b026a..0db9bec14 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt @@ -14,17 +14,11 @@ import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.event.PacketReceivedEvent -import net.mamoe.mirai.qqandroid.network.protocol.data.jce.GetFriendListReq -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.PacketFactory -import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger -import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList +import net.mamoe.mirai.qqandroid.network.protocol.packet.* import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket.LoginPacketResponse.* 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.io.* import kotlin.coroutines.CoroutineContext @@ -56,7 +50,9 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler is Captcha -> when (response) { is Captcha.Picture -> { - var result = bot.configuration.loginSolver.onSolvePicCaptcha(bot, response.data) + var result = response.data.withUse { + bot.configuration.loginSolver.onSolvePicCaptcha(bot, this) + } if (result == null || result.length != 4) { //refresh captcha result = "ABCD" @@ -325,19 +321,33 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler /** * 发送一个包, 并挂起直到接收到指定的返回包或超时(3000ms) */ - suspend fun <E : Packet> OutgoingPacket.sendAndExpect(timoutMillis: Long = 3000): E { + suspend fun <E : Packet> OutgoingPacket.sendAndExpect(timeoutMillis: Long = 3000, retry: Int = 1): E { + require(timeoutMillis > 0) { "timeoutMillis must > 0" } + require(retry >= 0) { "retry must >= 0" } + val handler = PacketListener(commandName = commandName, sequenceId = sequenceId) packetListeners.addLast(handler) bot.logger.info("Send: ${this.commandName}") + var lastException: Exception? = null + repeat(retry + 1) { + try { + return doSendAndReceive(timeoutMillis, handler) + } catch (e: Exception) { + lastException = e + } + } + throw lastException!! + } + + private suspend inline fun <E : Packet> OutgoingPacket.doSendAndReceive(timeoutMillis: Long = 3000, handler: PacketListener): E { channel.send(delegate) - return withTimeoutOrNull(timoutMillis) { + return withTimeoutOrNull(timeoutMillis) { @Suppress("UNCHECKED_CAST") handler.await() as E - // 不要 `withTimeout`. timeout 的异常会不知道去哪了. } ?: net.mamoe.mirai.qqandroid.utils.inline { packetListeners.remove(handler) - error("timeout when sending ${commandName}") + error("timeout when sending $commandName") } } diff --git a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/ContactImpl.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/ContactImpl.kt index b648de090..dcf769fe9 100644 --- a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/ContactImpl.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/ContactImpl.kt @@ -139,6 +139,9 @@ internal class QQImpl @PublishedApi internal constructor(bot: TIMPCBot, override } } + override val isOnline: Boolean + get() = true + override suspend fun queryProfile(): Profile = withTIMPCBot { RequestProfileDetailsPacket(bot.uin, id, sessionKey).sendAndExpect<RequestProfileDetailsResponse>().profile } diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/defaultLoginSolver.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/defaultLoginSolver.kt index dc631dfc0..c58779c47 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/defaultLoginSolver.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/defaultLoginSolver.kt @@ -15,12 +15,7 @@ actual var defaultLoginSolver: LoginSolver = object : LoginSolver() { error("should be implemented manually by you") } - override suspend fun onGetPhoneNumber(): String { + override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? { error("should be implemented manually by you") } - - override suspend fun onGetSMSVerifyCode(): String { - error("should be implemented manually by you") - } - } \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt index b4fdd1340..fbfdac520 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt @@ -34,6 +34,14 @@ actual class PlatformSocket : Closeable { @PublishedApi internal lateinit var readChannel: ByteReadChannel + actual suspend inline fun send(packet: ByteArray, offset: Int, length: Int) { + try { + writeChannel.writeFully(packet, offset, length) + } catch (e: Exception) { + throw SendPacketInternalException(e) + } + } + /** * @throws SendPacketInternalException */ diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt index f334e4c40..061420ffb 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt @@ -98,7 +98,15 @@ abstract class BotImpl<N : BotNetworkHandler> constructor( } _network = createNetworkHandler(this.coroutineContext) - _network.login() + while (true){ + try { + return _network.login() + } catch (e: Exception){ + e.logStacktrace("Exception when login") + } + delay(3000) + logger.warning("Login failed. Retrying in 3s...") + } } protected abstract fun createNetworkHandler(coroutineContext: CoroutineContext): N diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt index 7fea786a3..b7f8b823f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt @@ -13,6 +13,7 @@ interface Member : QQ, Contact { /** * 所在的群 */ + @WeakRefProperty val group: Group /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt index c7dfcfe23..289dcef79 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt @@ -21,10 +21,15 @@ import net.mamoe.mirai.data.Profile * @author Him188moe */ interface QQ : Contact, CoroutineScope { + /** + * 是否在线. 这个属性的值将会与服务器同步更新. + */ + val isOnline: Boolean + /** * 请求头像下载链接 */ - // @MiraiExperimentalAPI + // @MiraiExperimentalAPI //suspend fun queryAvatar(): AvatarLink /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt index ed9a245c2..d3eb05c20 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt @@ -12,6 +12,11 @@ import net.mamoe.mirai.utils.MiraiInternalAPI expect class PlatformSocket() : Closeable { suspend fun connect(serverHost: String, serverPort: Int) + /** + * @throws SendPacketInternalException + */ + suspend inline fun send(packet: ByteArray, offset: Int = 0, length: Int = packet.size - offset) + /** * @throws SendPacketInternalException */ diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/DefaultCaptchaSolverJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/DefaultCaptchaSolverJvm.kt index 4266a8898..6bcf26598 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/DefaultCaptchaSolverJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/DefaultCaptchaSolverJvm.kt @@ -29,36 +29,34 @@ actual var defaultLoginSolver: LoginSolver = DefaultLoginSolver() class DefaultLoginSolver : LoginSolver() { - override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? { - loginSolverLock.withLock { - val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() } - withContext(Dispatchers.IO) { - tempFile.createNewFile() - bot.logger.info("需要图片验证码登录, 验证码为 4 字母") - try { - tempFile.writeChannel().use { writeFully(data) } - bot.logger.info("将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}") - } catch (e: Exception) { - bot.logger.info("无法写出验证码文件(${e.message}), 请尝试查看以上字符图片") - } + override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? = loginSolverLock.withLock { + val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() } + withContext(Dispatchers.IO) { + tempFile.createNewFile() + bot.logger.info("需要图片验证码登录, 验证码为 4 字母") + try { + tempFile.writeChannel().use { writeFully(data) } + bot.logger.info("将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}") + } catch (e: Exception) { + bot.logger.info("无法写出验证码文件(${e.message}), 请尝试查看以上字符图片") + } - tempFile.inputStream().use { - val img = ImageIO.read(it) - if (img == null) { - bot.logger.info("无法创建字符图片. 请查看文件") - } else { - bot.logger.info(img.createCharImg()) - } + tempFile.inputStream().use { + val img = ImageIO.read(it) + if (img == null) { + bot.logger.info("无法创建字符图片. 请查看文件") + } else { + bot.logger.info(img.createCharImg()) } } - bot.logger.info("请输入 4 位字母验证码. 若要更换验证码, 请直接回车") - return readLine()?.takeUnless { it.isEmpty() || it.length != 4 }.also { - bot.logger.info("正在提交[" + it +"]中...") - } + } + bot.logger.info("请输入 4 位字母验证码. 若要更换验证码, 请直接回车") + return readLine()?.takeUnless { it.isEmpty() || it.length != 4 }.also { + bot.logger.info("正在提交[$it]中...") } } - override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? { + override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? = loginSolverLock.withLock { bot.logger.info("需要滑动验证码") bot.logger.info("请在任意浏览器中打开以下链接并完成验证码. ") bot.logger.info("完成后请输入任意字符 ") @@ -68,7 +66,7 @@ class DefaultLoginSolver : LoginSolver() { } } - override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? { + override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? = loginSolverLock.withLock { bot.logger.info("需要进行账户安全认证") bot.logger.info("该账户有[设备锁]/[不常用登陆地点]/[不常用设备登陆]的问题") bot.logger.info("完成以下账号认证即可成功登陆|理论本认证在mirai每个账户中最多出现1次") diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt index b4fdd1340..fbfdac520 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt @@ -34,6 +34,14 @@ actual class PlatformSocket : Closeable { @PublishedApi internal lateinit var readChannel: ByteReadChannel + actual suspend inline fun send(packet: ByteArray, offset: Int, length: Int) { + try { + writeChannel.writeFully(packet, offset, length) + } catch (e: Exception) { + throw SendPacketInternalException(e) + } + } + /** * @throws SendPacketInternalException */ diff --git a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt index 5574d630b..1492693fe 100644 --- a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt +++ b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt @@ -57,11 +57,7 @@ class MiraiService : Service() { TODO("not implemented") } - override suspend fun onGetPhoneNumber(): String { - TODO("not implemented") - } - - override suspend fun onGetSMSVerifyCode(): String { + override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? { TODO("not implemented") }