mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-27 04:30:08 +08:00
[core] Support solving device verification request by SMS:
- close #2190, helps #717 - deprecate `LoginSolver.onSolveUnsafeDeviceLoginVerify`, add `.onSolveDeviceVerification`
This commit is contained in:
parent
1691172667
commit
eb89b6348d
@ -5691,14 +5691,17 @@ public final class net/mamoe/mirai/network/NoStandardInputForCaptchaException :
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/network/RetryLaterException : net/mamoe/mirai/network/LoginFailedException {
|
||||
public synthetic fun <init> (Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun getCause ()Ljava/lang/Throwable;
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/network/UnsupportedSliderCaptchaException : net/mamoe/mirai/network/LoginFailedException {
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/network/UnsupportedSmsLoginException : net/mamoe/mirai/network/LoginFailedException {
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/network/WrongPasswordException : net/mamoe/mirai/network/LoginFailedException {
|
||||
}
|
||||
|
||||
@ -5934,6 +5937,27 @@ public final class net/mamoe/mirai/utils/DeviceInfoKt {
|
||||
public static final fun generateDeviceInfoData (Lnet/mamoe/mirai/utils/DeviceInfo;)[B
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/utils/DeviceVerificationRequests {
|
||||
public abstract fun getFallback ()Lnet/mamoe/mirai/utils/DeviceVerificationRequests$FallbackRequest;
|
||||
public abstract fun getPreferSms ()Z
|
||||
public abstract fun getSms ()Lnet/mamoe/mirai/utils/DeviceVerificationRequests$SmsRequest;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/utils/DeviceVerificationRequests$FallbackRequest {
|
||||
public abstract fun getUrl ()Ljava/lang/String;
|
||||
public abstract fun solved ()Lnet/mamoe/mirai/utils/DeviceVerificationResult;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/utils/DeviceVerificationRequests$SmsRequest {
|
||||
public abstract fun getCountryCode ()Ljava/lang/String;
|
||||
public abstract fun getPhoneNumber ()Ljava/lang/String;
|
||||
public abstract fun requestSms (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun solved (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceVerificationResult;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/utils/DeviceVerificationResult {
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/utils/DirectoryLogger : net/mamoe/mirai/utils/SimpleLogger {
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
public fun <init> (Ljava/lang/String;Ljava/io/File;)V
|
||||
@ -6122,6 +6146,7 @@ public abstract class net/mamoe/mirai/utils/LoginSolver {
|
||||
public static final field Default Lnet/mamoe/mirai/utils/LoginSolver;
|
||||
public fun <init> ()V
|
||||
public fun isSliderCaptchaSupported ()Z
|
||||
public fun onSolveDeviceVerification (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/utils/DeviceVerificationRequests;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun onSolvePicCaptcha (Lnet/mamoe/mirai/Bot;[BLkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun onSolveSliderCaptcha (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun onSolveUnsafeDeviceLoginVerify (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
|
@ -5691,14 +5691,17 @@ public final class net/mamoe/mirai/network/NoStandardInputForCaptchaException :
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/network/RetryLaterException : net/mamoe/mirai/network/LoginFailedException {
|
||||
public synthetic fun <init> (Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun getCause ()Ljava/lang/Throwable;
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/network/UnsupportedSliderCaptchaException : net/mamoe/mirai/network/LoginFailedException {
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/network/UnsupportedSmsLoginException : net/mamoe/mirai/network/LoginFailedException {
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/network/WrongPasswordException : net/mamoe/mirai/network/LoginFailedException {
|
||||
}
|
||||
|
||||
@ -5934,6 +5937,27 @@ public final class net/mamoe/mirai/utils/DeviceInfoKt {
|
||||
public static final fun generateDeviceInfoData (Lnet/mamoe/mirai/utils/DeviceInfo;)[B
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/utils/DeviceVerificationRequests {
|
||||
public abstract fun getFallback ()Lnet/mamoe/mirai/utils/DeviceVerificationRequests$FallbackRequest;
|
||||
public abstract fun getPreferSms ()Z
|
||||
public abstract fun getSms ()Lnet/mamoe/mirai/utils/DeviceVerificationRequests$SmsRequest;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/utils/DeviceVerificationRequests$FallbackRequest {
|
||||
public abstract fun getUrl ()Ljava/lang/String;
|
||||
public abstract fun solved ()Lnet/mamoe/mirai/utils/DeviceVerificationResult;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/utils/DeviceVerificationRequests$SmsRequest {
|
||||
public abstract fun getCountryCode ()Ljava/lang/String;
|
||||
public abstract fun getPhoneNumber ()Ljava/lang/String;
|
||||
public abstract fun requestSms (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun solved (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceVerificationResult;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/utils/DeviceVerificationResult {
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/utils/DirectoryLogger : net/mamoe/mirai/utils/SimpleLogger {
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
public fun <init> (Ljava/lang/String;Ljava/io/File;)V
|
||||
@ -6122,6 +6146,7 @@ public abstract class net/mamoe/mirai/utils/LoginSolver {
|
||||
public static final field Default Lnet/mamoe/mirai/utils/LoginSolver;
|
||||
public fun <init> ()V
|
||||
public fun isSliderCaptchaSupported ()Z
|
||||
public fun onSolveDeviceVerification (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/utils/DeviceVerificationRequests;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun onSolvePicCaptcha (Lnet/mamoe/mirai/Bot;[BLkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun onSolveSliderCaptcha (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun onSolveUnsafeDeviceLoginVerify (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
@ -6486,6 +6511,7 @@ public final class net/mamoe/mirai/utils/StandardCharImageLoginSolver : net/mamo
|
||||
public static final fun createBlocking (Lkotlin/jvm/functions/Function0;)Lnet/mamoe/mirai/utils/StandardCharImageLoginSolver;
|
||||
public static final fun createBlocking (Lkotlin/jvm/functions/Function0;Lnet/mamoe/mirai/utils/MiraiLogger;)Lnet/mamoe/mirai/utils/StandardCharImageLoginSolver;
|
||||
public fun isSliderCaptchaSupported ()Z
|
||||
public fun onSolveDeviceVerification (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/utils/DeviceVerificationRequests;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun onSolvePicCaptcha (Lnet/mamoe/mirai/Bot;[BLkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun onSolveSliderCaptcha (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun onSolveUnsafeDeviceLoginVerify (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
|
@ -10,69 +10,7 @@
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.network.LoginFailedException
|
||||
import net.mamoe.mirai.utils.LoginSolver.Companion.Default
|
||||
|
||||
|
||||
/**
|
||||
* 验证码, 设备锁解决器
|
||||
*
|
||||
* @see Default
|
||||
* @see BotConfiguration.loginSolver
|
||||
*/
|
||||
public actual abstract class LoginSolver public actual constructor() {
|
||||
/**
|
||||
* 处理图片验证码.
|
||||
*
|
||||
* 返回 `null` 以表示无法处理验证码, 将会刷新验证码或重试登录.
|
||||
* 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止
|
||||
*
|
||||
* @throws LoginFailedException
|
||||
*/
|
||||
public actual abstract suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String?
|
||||
|
||||
/**
|
||||
* 为 `true` 表示支持滑动验证码, 遇到滑动验证码时 mirai 会请求 [onSolveSliderCaptcha].
|
||||
* 否则会跳过滑动验证码并告诉服务器此客户端不支持, 有可能导致登录失败
|
||||
*/
|
||||
public actual open val isSliderCaptchaSupported: Boolean
|
||||
get() = System.getProperty("mirai.slider.captcha.supported") != null
|
||||
|
||||
/**
|
||||
* 处理滑动验证码.
|
||||
*
|
||||
* 返回 `null` 以表示无法处理验证码, 将会刷新验证码或重试登录.
|
||||
* 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止
|
||||
*
|
||||
* @throws LoginFailedException
|
||||
* @return 验证码解决成功后获得的 ticket.
|
||||
*/
|
||||
public actual abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String?
|
||||
|
||||
/**
|
||||
* 处理不安全设备验证.
|
||||
*
|
||||
* 返回值保留给将来使用. 目前在处理完成后返回任意内容 (包含 `null`) 均视为处理成功.
|
||||
* 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止.
|
||||
*
|
||||
* @return 任意内容. 返回值保留以供未来更新.
|
||||
* @throws LoginFailedException
|
||||
*/
|
||||
public actual abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String?
|
||||
|
||||
public actual companion object {
|
||||
/**
|
||||
* 当前平台默认的 [LoginSolver]. Android 端没有默认验证码实现, [Default] 总为 `null`.
|
||||
*/
|
||||
@JvmField
|
||||
public actual val Default: LoginSolver? = null
|
||||
|
||||
@Suppress("unused")
|
||||
@Deprecated("Binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
@DeprecatedSinceMirai(hiddenSince = "2.0") // maybe 2.0
|
||||
public actual fun getDefault(): LoginSolver = Default
|
||||
?: error("LoginSolver is not provided by default on your platform. Please specify by BotConfiguration.loginSolver")
|
||||
}
|
||||
|
||||
}
|
||||
internal actual object PlatformLoginSolverImplementations {
|
||||
actual val isSliderCaptchaSupported: Boolean by lazy { System.getProperty("mirai.slider.captcha.supported") != null }
|
||||
actual val default: LoginSolver? get() = null
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
package net.mamoe.mirai.network
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalApi
|
||||
import net.mamoe.mirai.utils.LoginSolver
|
||||
import net.mamoe.mirai.utils.MiraiInternalApi
|
||||
|
||||
|
||||
@ -47,8 +47,11 @@ public class NoServerAvailableException @MiraiInternalApi constructor(
|
||||
/**
|
||||
* 服务器要求稍后重试
|
||||
*/
|
||||
public class RetryLaterException @MiraiInternalApi constructor(override val cause: Throwable? = null) :
|
||||
LoginFailedException(false, "server requests retrial later")
|
||||
public class RetryLaterException @MiraiInternalApi constructor(
|
||||
message: String?,
|
||||
cause: Throwable? = null,
|
||||
killBot: Boolean = false
|
||||
) : LoginFailedException(killBot, message, cause)
|
||||
|
||||
/**
|
||||
* 无标准输入或 Kotlin 不支持此输入.
|
||||
@ -58,10 +61,10 @@ public class NoStandardInputForCaptchaException @MiraiInternalApi constructor(
|
||||
) : LoginFailedException(true, "no standard input for captcha")
|
||||
|
||||
/**
|
||||
* 需要短信验证时抛出. mirai 目前还不支持短信验证.
|
||||
* 需要强制短信验证, 且当前 [LoginSolver] 不支持时抛出.
|
||||
* @since 2.13
|
||||
*/
|
||||
@MiraiExperimentalApi("Will be removed when SMS login is supported")
|
||||
public class UnsupportedSMSLoginException(message: String?) : LoginFailedException(true, message)
|
||||
public class UnsupportedSmsLoginException(message: String?) : LoginFailedException(true, message)
|
||||
|
||||
/**
|
||||
* 无法完成滑块验证
|
||||
|
@ -7,12 +7,18 @@
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
@file:JvmName("LoginSolver_common")
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.network.LoginFailedException
|
||||
import net.mamoe.mirai.network.RetryLaterException
|
||||
import net.mamoe.mirai.network.UnsupportedSmsLoginException
|
||||
import net.mamoe.mirai.utils.LoginSolver.Companion.Default
|
||||
import kotlin.jvm.JvmField
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
/**
|
||||
* 验证码, 设备锁解决器
|
||||
@ -20,12 +26,18 @@ import kotlin.jvm.JvmField
|
||||
* @see Default
|
||||
* @see BotConfiguration.loginSolver
|
||||
*/
|
||||
public expect abstract class LoginSolver() {
|
||||
public abstract class LoginSolver {
|
||||
/**
|
||||
* 处理图片验证码.
|
||||
* 处理图片验证码, 返回图片验证码内容.
|
||||
*
|
||||
* 返回 `null` 以表示无法处理验证码, 将会刷新验证码或重试登录.
|
||||
* 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止
|
||||
*
|
||||
* ## 异常类型
|
||||
*
|
||||
* 抛出一个 [LoginFailedException] 以正常地终止登录, 并可建议系统进行重连或停止 bot (通过 [LoginFailedException.killBot]).
|
||||
* 例如抛出 [RetryLaterException] 可让 bot 重新进行一次登录.
|
||||
*
|
||||
* 抛出任意其他 [Throwable] 将视为验证码解决器的自身错误.
|
||||
*
|
||||
* @throws LoginFailedException
|
||||
*/
|
||||
@ -35,28 +47,72 @@ public expect abstract class LoginSolver() {
|
||||
* 为 `true` 表示支持滑动验证码, 遇到滑动验证码时 mirai 会请求 [onSolveSliderCaptcha].
|
||||
* 否则会跳过滑动验证码并告诉服务器此客户端不支持, 有可能导致登录失败
|
||||
*/
|
||||
public open val isSliderCaptchaSupported: Boolean
|
||||
public open val isSliderCaptchaSupported: Boolean get() = PlatformLoginSolverImplementations.isSliderCaptchaSupported
|
||||
|
||||
/**
|
||||
* 处理滑动验证码.
|
||||
*
|
||||
* 返回 `null` 以表示无法处理验证码, 将会刷新验证码或重试登录.
|
||||
* 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止
|
||||
*
|
||||
* ## 异常类型
|
||||
*
|
||||
* 抛出一个 [LoginFailedException] 以正常地终止登录, 并可建议系统进行重连或停止 bot (通过 [LoginFailedException.killBot]).
|
||||
* 例如抛出 [RetryLaterException] 可让 bot 重新进行一次登录.
|
||||
*
|
||||
* 抛出任意其他 [Throwable] 将视为验证码解决器的自身错误.
|
||||
*
|
||||
* @throws LoginFailedException
|
||||
* @return 验证码解决成功后获得的 ticket.
|
||||
*/
|
||||
public abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String?
|
||||
|
||||
/**
|
||||
* 处理设备验证.
|
||||
*
|
||||
* ## 异常类型
|
||||
*
|
||||
* 抛出一个 [LoginFailedException] 以正常地终止登录, 并可建议系统进行重连或停止 bot (通过 [LoginFailedException.killBot]).
|
||||
* 例如抛出 [RetryLaterException] 可让 bot 重新进行一次登录.
|
||||
*
|
||||
* 抛出任意其他 [Throwable] 将视为验证码解决器的自身错误.
|
||||
*
|
||||
* @since 验证结果, 可通过解决 [DeviceVerificationRequests] 获得.
|
||||
* @throws LoginFailedException
|
||||
* @since 2.13
|
||||
*/
|
||||
public open suspend fun onSolveDeviceVerification(
|
||||
bot: Bot,
|
||||
requests: DeviceVerificationRequests,
|
||||
): DeviceVerificationResult {
|
||||
requests.fallback?.let { fallback ->
|
||||
@Suppress("DEPRECATION")
|
||||
(onSolveUnsafeDeviceLoginVerify(bot, fallback.url))
|
||||
return fallback.solved()
|
||||
}
|
||||
throw UnsupportedSmsLoginException("This login session requires SMS verification, but current LoginSolver($this) does not support it.")
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理不安全设备验证.
|
||||
*
|
||||
* 返回值保留给将来使用. 目前在处理完成后返回任意内容 (包含 `null`) 均视为处理成功.
|
||||
* 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止.
|
||||
*
|
||||
* ## 异常类型
|
||||
*
|
||||
* 抛出一个 [LoginFailedException] 以正常地终止登录, 并可建议系统进行重连或停止 bot (通过 [LoginFailedException.killBot]).
|
||||
* 例如抛出 [RetryLaterException] 可让 bot 重新进行一次登录.
|
||||
*
|
||||
* 抛出任意其他 [Throwable] 将视为验证码解决器的自身错误.
|
||||
*
|
||||
* @return 任意内容. 返回值保留以供未来更新.
|
||||
* @throws LoginFailedException
|
||||
*/
|
||||
@Deprecated(
|
||||
"Please use onSolveDeviceVerification instead",
|
||||
level = DeprecationLevel.WARNING,
|
||||
replaceWith = ReplaceWith("onSolveDeviceVerification(bot, url, null)")
|
||||
) // softly
|
||||
@DeprecatedSinceMirai(warningSince = "2.13") // for hidden
|
||||
public abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String?
|
||||
|
||||
public companion object {
|
||||
@ -72,11 +128,103 @@ public expect abstract class LoginSolver() {
|
||||
* @return `SwingSolver` 或 `StandardCharImageLoginSolver` 或 `null`
|
||||
*/
|
||||
@JvmField
|
||||
public val Default: LoginSolver?
|
||||
public val Default: LoginSolver? = PlatformLoginSolverImplementations.default
|
||||
|
||||
@Suppress("unused")
|
||||
@Deprecated("Binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
public fun getDefault(): LoginSolver
|
||||
public fun getDefault(): LoginSolver = Default
|
||||
?: error("LoginSolver is not provided by default on your platform. Please specify by BotConfiguration.loginSolver")
|
||||
}
|
||||
}
|
||||
|
||||
internal expect object PlatformLoginSolverImplementations {
|
||||
val isSliderCaptchaSupported: Boolean
|
||||
val default: LoginSolver?
|
||||
}
|
||||
|
||||
/**
|
||||
* 属性 [sms] 为短信验证码验证方式, [fallback] 为其他验证方式.
|
||||
* 两个属性至少有一个不为 `null`, 在不为 `null` 时表示支持该验证方式. 可任意选用偏好的验证方式.
|
||||
*
|
||||
* @since 2.13
|
||||
*/
|
||||
@NotStableForInheritance
|
||||
public interface DeviceVerificationRequests {
|
||||
/**
|
||||
* 短信验证码方式. 在不为 `null` 时表示支持该验证方式.
|
||||
*/
|
||||
public val sms: SmsRequest?
|
||||
|
||||
/**
|
||||
* 其他验证方式. 在不为 `null` 时表示支持该验证方式.
|
||||
*/
|
||||
public val fallback: FallbackRequest?
|
||||
|
||||
/**
|
||||
* 服务器要求使用短信验证码. 此时可能仍可以尝试 [fallback].
|
||||
*/
|
||||
public val preferSms: Boolean
|
||||
|
||||
|
||||
/**
|
||||
* 服务器要求短信验证时提供的账号绑定的手机信息. 使用 [requestSms] 来请求发送验证码
|
||||
*
|
||||
* @since 2.13
|
||||
* @see LoginSolver.onSolveDeviceVerification
|
||||
*/
|
||||
@NotStableForInheritance
|
||||
public interface SmsRequest {
|
||||
/**
|
||||
* 手机号归属国家代码, 如中国为 86.
|
||||
* 在获取失败时会返回 `null`,但通常会获取到
|
||||
*/
|
||||
public val countryCode: String?
|
||||
|
||||
/**
|
||||
* 手机号码, 部分数字会被隐藏, 示例: `123*******1`.
|
||||
* 在获取失败时会返回 `null`, 但通常会获取到
|
||||
*/
|
||||
public val phoneNumber: String?
|
||||
|
||||
/**
|
||||
* 请求服务器发送短信到验证手机号
|
||||
*
|
||||
* @throws RetryLaterException 当请求过于频繁, 服务器拒绝请求时抛出
|
||||
*/
|
||||
@JvmBlockingBridge
|
||||
public suspend fun requestSms()
|
||||
|
||||
/**
|
||||
* 通知此请求已被解决. 获取 [DeviceVerificationResult] 用于返回 [LoginSolver.onSolveDeviceVerification].
|
||||
*/
|
||||
public fun solved(code: String): DeviceVerificationResult
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 其他验证方式.
|
||||
*
|
||||
* @since 2.13
|
||||
* @see LoginSolver.onSolveDeviceVerification
|
||||
*/
|
||||
@NotStableForInheritance
|
||||
public interface FallbackRequest {
|
||||
/**
|
||||
* HTTP URL. 可能需要在 QQ 浏览器中打开并人工操作.
|
||||
*/
|
||||
public val url: String
|
||||
|
||||
/**
|
||||
* 通知此请求已被解决. 获取 [DeviceVerificationResult] 用于返回 [LoginSolver.onSolveDeviceVerification].
|
||||
*/
|
||||
public fun solved(): DeviceVerificationResult
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备验证的验证结果. 请不要自行实现此接口, 而是通过解决 [DeviceVerificationRequests] 中的其中一种验证获得.
|
||||
*
|
||||
* @since 2.13
|
||||
* @see LoginSolver.onSolveDeviceVerification
|
||||
*/
|
||||
@NotStableForInheritance
|
||||
public interface DeviceVerificationResult
|
||||
|
@ -1,10 +1,10 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
|
||||
@ -12,15 +12,14 @@ package net.mamoe.mirai.utils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.internal.utils.SeleniumLoginSolver
|
||||
import net.mamoe.mirai.internal.utils.isSliderCaptchaSupportKind
|
||||
import net.mamoe.mirai.network.LoginFailedException
|
||||
import net.mamoe.mirai.network.NoStandardInputForCaptchaException
|
||||
import net.mamoe.mirai.utils.LoginSolver.Companion.Default
|
||||
import net.mamoe.mirai.utils.StandardCharImageLoginSolver.Companion.createBlocking
|
||||
import java.awt.Image
|
||||
import java.awt.image.BufferedImage
|
||||
@ -29,67 +28,10 @@ import javax.imageio.ImageIO
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
|
||||
/**
|
||||
* 验证码, 设备锁解决器
|
||||
*
|
||||
* @see Default
|
||||
* @see BotConfiguration.loginSolver
|
||||
*/
|
||||
public actual abstract class LoginSolver public actual constructor() {
|
||||
/**
|
||||
* 处理图片验证码.
|
||||
*
|
||||
* 返回 `null` 以表示无法处理验证码, 将会刷新验证码或重试登录.
|
||||
* 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止
|
||||
*
|
||||
* @throws LoginFailedException
|
||||
*/
|
||||
public actual abstract suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String?
|
||||
|
||||
/**
|
||||
* 为 `true` 表示支持滑动验证码, 遇到滑动验证码时 mirai 会请求 [onSolveSliderCaptcha].
|
||||
* 否则会跳过滑动验证码并告诉服务器此客户端不支持, 有可能导致登录失败
|
||||
*/
|
||||
public actual open val isSliderCaptchaSupported: Boolean
|
||||
get() = isSliderCaptchaSupportKind ?: true
|
||||
|
||||
/**
|
||||
* 处理滑动验证码.
|
||||
*
|
||||
* 返回 `null` 以表示无法处理验证码, 将会刷新验证码或重试登录.
|
||||
* 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止
|
||||
*
|
||||
* @throws LoginFailedException
|
||||
* @return 验证码解决成功后获得的 ticket.
|
||||
*/
|
||||
public actual abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String?
|
||||
|
||||
/**
|
||||
* 处理不安全设备验证.
|
||||
*
|
||||
* 返回值保留给将来使用. 目前在处理完成后返回任意内容 (包含 `null`) 均视为处理成功.
|
||||
* 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止.
|
||||
*
|
||||
* @return 任意内容. 返回值保留以供未来更新.
|
||||
* @throws LoginFailedException
|
||||
*/
|
||||
public actual abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String?
|
||||
|
||||
public actual companion object {
|
||||
/**
|
||||
* 当前平台默认的 [LoginSolver]。
|
||||
*
|
||||
* 检测策略:
|
||||
* 1. 检测 `android.util.Log`, 如果存在, 返回 `null`.
|
||||
* 2. 检测 JVM 属性 `mirai.no-desktop`. 若存在, 返回 [StandardCharImageLoginSolver]
|
||||
* 3. 检测 JVM 桌面环境, 若支持, 返回 [SwingSolver]
|
||||
* 4. 返回 [StandardCharImageLoginSolver]
|
||||
*
|
||||
* @return [SwingSolver] 或 [StandardCharImageLoginSolver] 或 `null`
|
||||
*/
|
||||
@JvmField
|
||||
public actual val Default: LoginSolver? = when (WindowHelperJvm.platformKind) {
|
||||
internal actual object PlatformLoginSolverImplementations {
|
||||
actual val isSliderCaptchaSupported: Boolean get() = isSliderCaptchaSupportKind ?: true
|
||||
actual val default: LoginSolver? by lazy {
|
||||
when (WindowHelperJvm.platformKind) {
|
||||
WindowHelperJvm.PlatformKind.ANDROID -> null
|
||||
WindowHelperJvm.PlatformKind.SWING -> {
|
||||
when (isSliderCaptchaSupportKind) {
|
||||
@ -99,16 +41,9 @@ public actual abstract class LoginSolver public actual constructor() {
|
||||
}
|
||||
WindowHelperJvm.PlatformKind.CLI -> StandardCharImageLoginSolver()
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@Deprecated("Binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
public actual fun getDefault(): LoginSolver = Default
|
||||
?: error("LoginSolver is not provided by default on your platform. Please specify by BotConfiguration.loginSolver")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CLI 环境 [LoginSolver]. 将验证码图片转为字符画并通过 `output` 输出, [input] 获取用户输入.
|
||||
*
|
||||
@ -136,8 +71,7 @@ public class StandardCharImageLoginSolver @JvmOverloads constructor(
|
||||
|
||||
override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? = loginSolverLock.withLock {
|
||||
val logger = loggerSupplier(bot)
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
(withContext(Dispatchers.IO) {
|
||||
runInterruptible(Dispatchers.IO) {
|
||||
val tempFile: File = File.createTempFile("tmp", ".png").apply { deleteOnExit() }
|
||||
tempFile.createNewFile()
|
||||
logger.info { "[PicCaptcha] 需要图片验证码登录, 验证码为 4 字母" }
|
||||
@ -165,7 +99,7 @@ public class StandardCharImageLoginSolver @JvmOverloads constructor(
|
||||
logger.warning("[PicCaptcha] Failed to create char-image. Please see the file.", throwable)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
logger.info { "[PicCaptcha] 请输入 4 位字母验证码. 若要更换验证码, 请直接回车" }
|
||||
logger.info { "[PicCaptcha] Please type 4-letter captcha. Press Enter directly to refresh." }
|
||||
return input().takeUnless { it.isEmpty() || it.length != 4 }.also {
|
||||
@ -211,13 +145,69 @@ public class StandardCharImageLoginSolver @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String = loginSolverLock.withLock {
|
||||
@Suppress("DuplicatedCode")
|
||||
override suspend fun onSolveDeviceVerification(
|
||||
bot: Bot, requests: DeviceVerificationRequests
|
||||
): DeviceVerificationResult {
|
||||
val logger = loggerSupplier(bot)
|
||||
logger.info { "[UnsafeLogin] 当前登录环境不安全,服务器要求账户认证。请在 QQ 浏览器打开 $url 并完成验证后输入任意字符。" }
|
||||
logger.info { "[UnsafeLogin] Account verification required by the server. Please open $url in QQ browser and complete challenge, then type anything here to submit." }
|
||||
return input().also {
|
||||
logger.info { "[UnsafeLogin] 正在提交中..." }
|
||||
logger.info { "[UnsafeLogin] Submitting..." }
|
||||
requests.sms?.let { req ->
|
||||
solveSms(logger, req)?.let { return it }
|
||||
}
|
||||
requests.fallback?.let { fallback ->
|
||||
solveFallback(logger, fallback.url)
|
||||
return fallback.solved()
|
||||
}
|
||||
error("User rejected SMS login while fallback login method not available.")
|
||||
}
|
||||
|
||||
private suspend fun solveSms(
|
||||
logger: MiraiLogger, request: DeviceVerificationRequests.SmsRequest
|
||||
): DeviceVerificationResult? = loginSolverLock.withLock {
|
||||
val countryCode = request.countryCode
|
||||
val phoneNumber = request.phoneNumber
|
||||
if (countryCode != null && phoneNumber != null) {
|
||||
logger.info("一条短信验证码将发送到你的手机 (+$countryCode) $phoneNumber. 运营商可能会收取正常短信费用, 是否继续? 输入 yes 继续, 输入其他终止并尝试其他验证方式.")
|
||||
logger.info(
|
||||
"A verification code will be send to your phone (+$countryCode) $phoneNumber, which may be charged normally, do you wish to continue? Type yes to continue, type others to cancel and try other methods."
|
||||
)
|
||||
} else {
|
||||
logger.info("一条短信验证码将发送到你的手机 (无法获取到手机号码). 运营商可能会收取正常短信费用, 是否继续? 输入 yes 继续, 输入其他终止并尝试其他验证方式.")
|
||||
logger.info(
|
||||
"A verification code will be send to your phone (failed to get phone number), " + "which may be charged normally by your carrier, " + "do you wish to continue? Type yes to continue, type others to cancel and try other methods."
|
||||
)
|
||||
}
|
||||
val answer = input().trim()
|
||||
return if (answer.equals("yes", ignoreCase = true)) {
|
||||
logger.info("Attempting SMS verification.")
|
||||
request.requestSms()
|
||||
logger.info("Please enter code: ")
|
||||
val code = input().trim()
|
||||
logger.info("Continuing with code '$code'.")
|
||||
request.solved(code)
|
||||
} else {
|
||||
logger.info("Cancelled.")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
"Please use onSolveDeviceVerification instead",
|
||||
replaceWith = ReplaceWith("onSolveDeviceVerification(bot, url, null)"),
|
||||
level = DeprecationLevel.WARNING
|
||||
)
|
||||
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String =
|
||||
solveFallback(loggerSupplier(bot), url)
|
||||
|
||||
private suspend fun solveFallback(
|
||||
logger: MiraiLogger, url: String
|
||||
): String {
|
||||
return loginSolverLock.withLock {
|
||||
logger.info { "[UnsafeLogin] 当前登录环境不安全,服务器要求账户认证。请在 QQ 浏览器打开 $url 并完成验证后输入任意字符。" }
|
||||
logger.info { "[UnsafeLogin] Account verification required by the server. Please open $url in QQ browser and complete challenge, then type anything here to submit." }
|
||||
input().also {
|
||||
logger.info { "[UnsafeLogin] 正在提交中..." }
|
||||
logger.info { "[UnsafeLogin] Submitting..." }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -299,4 +289,4 @@ private fun BufferedImage.createCharImg(outputWidth: Int = 100, ignoreRate: Doub
|
||||
append(line.substring(minXPos, maxXPos)).append("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
@ -24,6 +24,8 @@ import java.awt.image.BufferedImage
|
||||
import java.net.URI
|
||||
import javax.imageio.ImageIO
|
||||
import javax.swing.*
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
@MiraiExperimentalApi
|
||||
public object SwingSolver : LoginSolver() {
|
||||
@ -98,8 +100,106 @@ public object SwingSolver : LoginSolver() {
|
||||
return@coroutineScope solver.openAndWait().takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
override suspend fun onSolveDeviceVerification(
|
||||
bot: Bot,
|
||||
requests: DeviceVerificationRequests
|
||||
): DeviceVerificationResult {
|
||||
requests.sms?.let { req ->
|
||||
solveSms(bot, req)?.let { return it }
|
||||
}
|
||||
requests.fallback?.let { fallback ->
|
||||
solveFallback(bot, fallback.url)
|
||||
return fallback.solved()
|
||||
}
|
||||
error("User rejected SMS login while fallback login method not available.")
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
"Please use onSolveDeviceVerification instead",
|
||||
replaceWith = ReplaceWith("onSolveDeviceVerification(bot, url, null)"),
|
||||
level = DeprecationLevel.WARNING
|
||||
)
|
||||
public override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String {
|
||||
val title = "Mirai UnsafeDeviceLoginVerify(${bot.id})"
|
||||
return solveFallback(bot, url)
|
||||
}
|
||||
|
||||
private suspend fun solveSms(bot: Bot, request: DeviceVerificationRequests.SmsRequest): DeviceVerificationResult? =
|
||||
coroutineScope {
|
||||
val smsRequester = object {
|
||||
var lastRequested = 0L
|
||||
|
||||
fun requestSms(parentComponent: Component) = launch {
|
||||
// oh, so shit code
|
||||
|
||||
val diff = (System.currentTimeMillis() - lastRequested).milliseconds
|
||||
if (diff < 1.minutes) {
|
||||
parentComponent.createTip(
|
||||
"""请求过于频繁, 请在 ${1.minutes - diff} 秒后再试""".trimIndent()
|
||||
).openAndWait()
|
||||
return@launch
|
||||
}
|
||||
|
||||
lastRequested = System.currentTimeMillis()
|
||||
kotlin.runCatching {
|
||||
request.requestSms()
|
||||
}.fold(
|
||||
onSuccess = {
|
||||
parentComponent.createTip(
|
||||
"""发送验证码成功, 请注意查收. 若未收到, 可在一分钟后重试.""".trimIndent()
|
||||
).openAndWait()
|
||||
},
|
||||
onFailure = {
|
||||
parentComponent.createTip(
|
||||
"<html>发送验证码失败.<br/><br/>${
|
||||
it.stackTraceToString().replace("\n", "<br/>").replace("\r", "")
|
||||
}"
|
||||
).openAndWait()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
val title = "Mirai Device Verification (${bot.id})"
|
||||
val phoneNumber = request.phoneNumber
|
||||
val countryCode = request.countryCode
|
||||
val phoneNumberTip = if (phoneNumber != null && countryCode != null)
|
||||
"""(+$countryCode) $phoneNumber"""
|
||||
else "(无法获取到手机号码)"
|
||||
|
||||
|
||||
val code = SwingLoginSolver(
|
||||
title, "",
|
||||
arrayOf(
|
||||
"",
|
||||
JButton("发送验证码").onClick {
|
||||
smsRequester.requestSms(this)
|
||||
},
|
||||
"",
|
||||
JLabel("验证码 (输入完成后按回车):")
|
||||
),
|
||||
hiddenInput = false,
|
||||
topComponent = JLabel(
|
||||
"""
|
||||
<html>
|
||||
需要进行短信验证码验证<br>
|
||||
一条短信验证码将发送到你的手机 $phoneNumberTip<br>
|
||||
运营商可能会收取正常短信费用<br>
|
||||
""".trimIndent()
|
||||
)
|
||||
).openAndWait().trim().ifEmpty { return@coroutineScope null }
|
||||
request.solved(code)
|
||||
}
|
||||
|
||||
private fun Component.createTip(tip: String) = SwingLoginSolver(
|
||||
"提示", "",
|
||||
arrayOf("", JLabel()),
|
||||
hiddenInput = true,
|
||||
topComponent = JLabel(tip),
|
||||
parentComponent = this,
|
||||
)
|
||||
|
||||
private suspend fun solveFallback(bot: Bot, url: String): String {
|
||||
val title = "Mirai Device Verification (${bot.id})"
|
||||
return SwingLoginSolver(
|
||||
title, "",
|
||||
arrayOf(
|
||||
@ -109,12 +209,12 @@ public object SwingSolver : LoginSolver() {
|
||||
hiddenInput = true,
|
||||
topComponent = JLabel(
|
||||
"""
|
||||
<html>
|
||||
需要进行账户安全认证<br>
|
||||
该账户有设备锁/不常用登录地点/不常用设备登录的问题<br>
|
||||
请在<b>手机 QQ</b> 打开下面链接
|
||||
成功后请关闭该窗口
|
||||
""".trimIndent()
|
||||
<html>
|
||||
需要进行账户安全认证<br>
|
||||
该账户有设备锁/不常用登录地点/不常用设备登录的问题<br>
|
||||
请在<b>手机 QQ</b> 打开下面链接
|
||||
成功后请关闭该窗口
|
||||
""".trimIndent()
|
||||
)
|
||||
).openAndWait()
|
||||
}
|
||||
@ -124,7 +224,6 @@ public object SwingSolver : LoginSolver() {
|
||||
// 隔离类代码
|
||||
// 在 jvm 中, 使用 WindowHelperJvm 不会加载 SwingSolverKt
|
||||
// 不会触发各种 NoDefClassError
|
||||
@Suppress("DEPRECATION")
|
||||
internal object WindowHelperJvm {
|
||||
enum class PlatformKind {
|
||||
ANDROID,
|
||||
|
@ -9,77 +9,7 @@
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.network.LoginFailedException
|
||||
import net.mamoe.mirai.utils.LoginSolver.Companion.Default
|
||||
|
||||
/**
|
||||
* 验证码, 设备锁解决器
|
||||
*
|
||||
* @see Default
|
||||
* @see BotConfiguration.loginSolver
|
||||
*/
|
||||
public actual abstract class LoginSolver actual constructor() {
|
||||
/**
|
||||
* 处理图片验证码.
|
||||
*
|
||||
* 返回 `null` 以表示无法处理验证码, 将会刷新验证码或重试登录.
|
||||
* 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止
|
||||
*
|
||||
* @throws LoginFailedException
|
||||
*/
|
||||
public actual abstract suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String?
|
||||
|
||||
/**
|
||||
* 为 `true` 表示支持滑动验证码, 遇到滑动验证码时 mirai 会请求 [onSolveSliderCaptcha].
|
||||
* 否则会跳过滑动验证码并告诉服务器此客户端不支持, 有可能导致登录失败
|
||||
*/
|
||||
public actual open val isSliderCaptchaSupported: Boolean
|
||||
get() = false
|
||||
|
||||
/**
|
||||
* 处理滑动验证码.
|
||||
*
|
||||
* 返回 `null` 以表示无法处理验证码, 将会刷新验证码或重试登录.
|
||||
* 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止
|
||||
*
|
||||
* @throws LoginFailedException
|
||||
* @return 验证码解决成功后获得的 ticket.
|
||||
*/
|
||||
public actual abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String?
|
||||
|
||||
/**
|
||||
* 处理不安全设备验证.
|
||||
*
|
||||
* 返回值保留给将来使用. 目前在处理完成后返回任意内容 (包含 `null`) 均视为处理成功.
|
||||
* 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止.
|
||||
*
|
||||
* @return 任意内容. 返回值保留以供未来更新.
|
||||
* @throws LoginFailedException
|
||||
*/
|
||||
public actual abstract suspend fun onSolveUnsafeDeviceLoginVerify(
|
||||
bot: Bot,
|
||||
url: String
|
||||
): String?
|
||||
|
||||
public actual companion object {
|
||||
/**
|
||||
* 当前平台默认的 [LoginSolver]。
|
||||
*
|
||||
* 检测策略:
|
||||
* 1. 若是 `mirai-core-api-android` 或 `android.util.Log` 存在, 返回 `null`.
|
||||
* 2. 检测 JVM 属性 `mirai.no-desktop`. 若存在, 返回 `StandardCharImageLoginSolver`
|
||||
* 3. 检测 JVM 桌面环境, 若支持, 返回 `SwingSolver`
|
||||
* 4. 返回 `StandardCharImageLoginSolver`
|
||||
*
|
||||
* @return `SwingSolver` 或 `StandardCharImageLoginSolver` 或 `null`
|
||||
*/
|
||||
public actual val Default: LoginSolver?
|
||||
get() = null
|
||||
|
||||
@Deprecated("Binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
@Suppress("unused")
|
||||
public actual fun getDefault(): LoginSolver = Default
|
||||
?: error("LoginSolver is not provided by default on your platform. Please specify by BotConfiguration.loginSolver")
|
||||
}
|
||||
internal actual object PlatformLoginSolverImplementations {
|
||||
actual val isSliderCaptchaSupported: Boolean get() = false
|
||||
actual val default: LoginSolver? get() = null
|
||||
}
|
@ -155,6 +155,7 @@ internal open class QQAndroidClient(
|
||||
|
||||
var t530: ByteArray? = null
|
||||
var t528: ByteArray? = null
|
||||
var t174: ByteArray? = null
|
||||
|
||||
/**
|
||||
* t186
|
||||
|
@ -19,14 +19,17 @@ import net.mamoe.mirai.internal.network.component.ComponentKey
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandler
|
||||
import net.mamoe.mirai.internal.network.handler.selector.NetworkException
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.DeviceVerificationResultImpl
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.SmsDeviceVerificationResult
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.UrlDeviceVerificationResult
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin.Login.LoginPacketResponse
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin.Login.LoginPacketResponse.Captcha
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin10
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin2
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin20
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin9
|
||||
import net.mamoe.mirai.network.*
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.*
|
||||
import net.mamoe.mirai.network.LoginFailedException
|
||||
import net.mamoe.mirai.network.RetryLaterException
|
||||
import net.mamoe.mirai.network.UnsupportedSliderCaptchaException
|
||||
import net.mamoe.mirai.network.WrongPasswordException
|
||||
import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol
|
||||
import net.mamoe.mirai.utils.LoginSolver
|
||||
import net.mamoe.mirai.utils.info
|
||||
@ -245,9 +248,19 @@ internal class SsoProcessorImpl(
|
||||
response = WtLogin20(client).sendAndExpect()
|
||||
}
|
||||
|
||||
is LoginPacketResponse.UnsafeLogin -> {
|
||||
loginSolverNotNull().onSolveUnsafeDeviceLoginVerify(bot, response.url)
|
||||
response = WtLogin9(client, allowSlider).sendAndExpect()
|
||||
is LoginPacketResponse.VerificationNeeded -> {
|
||||
val result = loginSolverNotNull().onSolveDeviceVerification(
|
||||
bot, response.requests
|
||||
)
|
||||
check(result is DeviceVerificationResultImpl)
|
||||
response = when (result) {
|
||||
is UrlDeviceVerificationResult -> {
|
||||
WtLogin9(client, allowSlider).sendAndExpect()
|
||||
}
|
||||
is SmsDeviceVerificationResult -> {
|
||||
WtLogin7(client, result.token, result.code).sendAndExpect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is Captcha.Picture -> {
|
||||
@ -290,21 +303,22 @@ internal class SsoProcessorImpl(
|
||||
|
||||
is LoginPacketResponse.Error -> {
|
||||
if (response.message.contains("0x9a")) { //Error(title=登录失败, message=请你稍后重试。(0x9a), errorInfo=)
|
||||
collectThrow(RetryLaterException(IllegalStateException("Login failed: $response")))
|
||||
collectThrow(RetryLaterException("Login failed: $response"))
|
||||
}
|
||||
val msg = response.toString()
|
||||
collectThrow(WrongPasswordException(buildString(capacity = msg.length) {
|
||||
append(msg)
|
||||
if (msg.contains("当前上网环境异常")) { // Error(title=禁止登录, message=当前上网环境异常,请更换网络环境或在常用设备上登录或稍后再试。, errorInfo=)
|
||||
append(", tips=若频繁出现, 请尝试开启设备锁")
|
||||
append(", mirai 提示: 若频繁出现, 请尝试开启设备锁")
|
||||
}
|
||||
if (msg.contains("当前登录存在安全风险")) { // Error(title=禁止登录, message=当前上网环境异常,请更换网络环境或在常用设备上登录或稍后再试。, errorInfo=)
|
||||
append(", mirai 提示: 这可能是尝试登录次数过多导致的, 请等待一段时间后再试")
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
is LoginPacketResponse.SMSVerifyCodeNeeded -> {
|
||||
val message = "SMS required: $response, which isn't yet supported"
|
||||
logger.error(message)
|
||||
collectThrow(UnsupportedSMSLoginException(message))
|
||||
is LoginPacketResponse.SmsRequestSuccess -> {
|
||||
error("Unexpected response: $response")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,20 +27,20 @@ import kotlin.random.Random
|
||||
|
||||
@kotlin.Suppress("unused")
|
||||
internal class OutgoingPacketWithRespType<R : Packet?> constructor(
|
||||
name: String?,
|
||||
remark: String?,
|
||||
commandName: String,
|
||||
sequenceId: Int,
|
||||
delegate: ByteReadPacket
|
||||
) : OutgoingPacket(name, commandName, sequenceId, delegate)
|
||||
) : OutgoingPacket(remark, commandName, sequenceId, delegate)
|
||||
|
||||
internal open class OutgoingPacket constructor(
|
||||
name: String?,
|
||||
remark: String?,
|
||||
val commandName: String,
|
||||
val sequenceId: Int,
|
||||
delegate: ByteReadPacket
|
||||
) {
|
||||
val delegate = delegate.readBytes()
|
||||
val displayName: String = if (name == null) commandName else "$commandName($name)"
|
||||
val displayName: String = if (remark == null) commandName else "$commandName($remark)"
|
||||
}
|
||||
|
||||
internal class IncomingPacket private constructor(
|
||||
@ -78,7 +78,7 @@ internal class IncomingPacket private constructor(
|
||||
internal inline fun <R : Packet?> OutgoingPacketFactory<R>.buildOutgoingUniPacket(
|
||||
client: QQAndroidClient,
|
||||
bodyType: Byte = 1, // 1: PB?
|
||||
name: String? = this.commandName,
|
||||
remark: String? = this.commandName,
|
||||
commandName: String = this.commandName,
|
||||
key: ByteArray = client.wLoginSigInfo.d2Key,
|
||||
extraData: ByteReadPacket = BRP_STUB,
|
||||
@ -86,7 +86,7 @@ internal inline fun <R : Packet?> OutgoingPacketFactory<R>.buildOutgoingUniPacke
|
||||
body: BytePacketBuilder.(sequenceId: Int) -> Unit
|
||||
): OutgoingPacketWithRespType<R> {
|
||||
|
||||
return OutgoingPacketWithRespType(name, commandName, sequenceId, buildPacket {
|
||||
return OutgoingPacketWithRespType(remark, commandName, sequenceId, buildPacket {
|
||||
writeIntLVPacket(lengthOffset = { it + 4 }) {
|
||||
writeInt(0x0B)
|
||||
writeByte(bodyType)
|
||||
@ -173,14 +173,14 @@ internal inline fun <R : Packet?> OutgoingPacketFactory<R>.buildLoginOutgoingPac
|
||||
client: QQAndroidClient,
|
||||
bodyType: Byte,
|
||||
extraData: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
name: String? = null,
|
||||
remark: String? = null,
|
||||
commandName: String = this.commandName,
|
||||
key: ByteArray = KEY_16_ZEROS,
|
||||
body: BytePacketBuilder.(sequenceId: Int) -> Unit
|
||||
): OutgoingPacketWithRespType<R> {
|
||||
val sequenceId: Int = client.nextSsoSequenceId()
|
||||
|
||||
return OutgoingPacketWithRespType(name, commandName, sequenceId, buildPacket {
|
||||
return OutgoingPacketWithRespType(remark, commandName, sequenceId, buildPacket {
|
||||
writeIntLVPacket(lengthOffset = { it + 4 }) {
|
||||
writeInt(0x00_00_00_0A)
|
||||
writeByte(bodyType)
|
||||
|
@ -285,13 +285,14 @@ internal fun BytePacketBuilder.t17a(
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BytePacketBuilder.t197(
|
||||
value: ByteArray
|
||||
) {
|
||||
internal fun BytePacketBuilder.t197() {
|
||||
writeShort(0x197)
|
||||
writeShortLVPacket {
|
||||
writeFully(value)
|
||||
}
|
||||
writeFully(byteArrayOf(0, 1, 0))
|
||||
}
|
||||
|
||||
internal fun BytePacketBuilder.t198() {
|
||||
writeShort(0x198)
|
||||
writeFully(byteArrayOf(0, 1, 0))
|
||||
}
|
||||
|
||||
internal fun BytePacketBuilder.t19e(
|
||||
|
@ -55,7 +55,7 @@ internal class PbMessageSvc {
|
||||
|
||||
return buildOutgoingUniPacket(
|
||||
client,
|
||||
name = "PbMsgWithDraw(" +
|
||||
remark = "PbMsgWithDraw(" +
|
||||
"group=$groupCode, " +
|
||||
"seq=${messageSequenceId.joinToString(separator = ",")}, " +
|
||||
"rand=${messageRandom.joinToString(separator = ",")}" +
|
||||
@ -98,7 +98,7 @@ internal class PbMessageSvc {
|
||||
|
||||
return buildOutgoingUniPacket(
|
||||
client,
|
||||
name = "PbMsgWithDraw(" +
|
||||
remark = "PbMsgWithDraw(" +
|
||||
"groupTemp=$toUin, " +
|
||||
"seq=${messageSequenceId.joinToString(separator = ",")}, " +
|
||||
"rand=${messageRandom.joinToString(separator = ",")}, " +
|
||||
@ -147,7 +147,7 @@ internal class PbMessageSvc {
|
||||
|
||||
return buildOutgoingUniPacket(
|
||||
client,
|
||||
name = "PbMsgWithDraw(" +
|
||||
remark = "PbMsgWithDraw(" +
|
||||
"friend=$toUin, " +
|
||||
"seq=${messageSequenceId.joinToString(separator = ",")}, " +
|
||||
"rand=${messageRandom.joinToString(separator = ",")}, " +
|
||||
|
@ -200,7 +200,7 @@ internal class StatSvc {
|
||||
bodyType = 1,
|
||||
extraData = client.wLoginSigInfo.d2.data,
|
||||
key = client.wLoginSigInfo.d2Key,
|
||||
name = name,
|
||||
remark = name,
|
||||
) { sequenceId ->
|
||||
writeSsoPacket(
|
||||
client, subAppId = client.subAppId, commandName = commandName,
|
||||
|
@ -14,56 +14,98 @@ import io.ktor.utils.io.core.*
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.event.AbstractEvent
|
||||
import net.mamoe.mirai.event.events.BotEvent
|
||||
import net.mamoe.mirai.internal.AbstractBot
|
||||
import net.mamoe.mirai.internal.QQAndroidBot
|
||||
import net.mamoe.mirai.internal.network.*
|
||||
import net.mamoe.mirai.internal.network.DebuggingProperties.SHOW_TLV_MAP_ON_LOGIN_SUCCESS
|
||||
import net.mamoe.mirai.internal.network.handler.logger
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.*
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin8
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLoginExt
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.analysisTlv0x531
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.orEmpty
|
||||
import net.mamoe.mirai.internal.utils.crypto.TEA
|
||||
import net.mamoe.mirai.internal.utils.printStructure
|
||||
import net.mamoe.mirai.network.RetryLaterException
|
||||
import net.mamoe.mirai.network.WrongPasswordException
|
||||
import net.mamoe.mirai.utils.*
|
||||
import net.mamoe.mirai.utils.structureToString
|
||||
|
||||
|
||||
internal class SmsVerifyInfo(
|
||||
override val countryCode: String?, // 86
|
||||
override val phoneNumber: String?, // 123*******1
|
||||
private val token: ByteArray, // t174
|
||||
private val bot: AbstractBot,
|
||||
) : DeviceVerificationRequests.SmsRequest {
|
||||
override suspend fun requestSms() {
|
||||
val result = try {
|
||||
bot.network.sendAndExpect(WtLogin8(bot.client, token))
|
||||
} catch (e: Exception) {
|
||||
bot.logger.warning("Exception while requesting SMS.", e)
|
||||
throw e
|
||||
}
|
||||
if (result !is WtLogin.Login.LoginPacketResponse.SmsRequestSuccess) {
|
||||
bot.logger.warning("Failed requestSms, result = $result")
|
||||
|
||||
if (result is WtLogin.Login.LoginPacketResponse.Error) {
|
||||
when (result.code) {
|
||||
161, 162 -> {
|
||||
// 今日操作次数过多,请等待一天后再试。
|
||||
throw RetryLaterException(
|
||||
result.message,
|
||||
null,
|
||||
killBot = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw WrongPasswordException("Failed requestSms, result = $result")
|
||||
}
|
||||
}
|
||||
|
||||
override fun solved(code: String): DeviceVerificationResult {
|
||||
return SmsDeviceVerificationResult(code, token)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "SmsVerifyInfo(+$countryCode $phoneNumber)"
|
||||
}
|
||||
}
|
||||
|
||||
internal class FallbackRequestImpl(override val url: String) : DeviceVerificationRequests.FallbackRequest {
|
||||
override fun solved(): DeviceVerificationResult {
|
||||
return UrlDeviceVerificationResult
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "FallbackRequestImpl($url)"
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed interface DeviceVerificationResultImpl : DeviceVerificationResult
|
||||
|
||||
internal object UrlDeviceVerificationResult : DeviceVerificationResultImpl {
|
||||
override fun toString(): String {
|
||||
return "UrlVerificationResult"
|
||||
}
|
||||
}
|
||||
|
||||
internal class SmsDeviceVerificationResult(
|
||||
val code: String,
|
||||
val token: ByteArray, // t174
|
||||
) : DeviceVerificationResultImpl {
|
||||
override fun toString(): String {
|
||||
return "SmsVerificationResult(code=$code, token=${token.toUHexString("")})"
|
||||
}
|
||||
}
|
||||
|
||||
internal class WtLogin {
|
||||
/**
|
||||
* OicqRequest
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
internal object Login : OutgoingPacketFactory<Login.LoginPacketResponse>("wtlogin.login"), WtLoginExt {
|
||||
|
||||
/**
|
||||
* 提交 SMS
|
||||
*/
|
||||
object SubCommand7 {
|
||||
operator fun invoke(
|
||||
client: QQAndroidClient
|
||||
) = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
|
||||
writeSsoPacket(
|
||||
client,
|
||||
client.subAppId,
|
||||
commandName,
|
||||
sequenceId = sequenceId,
|
||||
unknownHex = "01 00 00 00 00 00 00 00 00 00 01 00"
|
||||
) {
|
||||
writeOicqRequestPacket(client, commandId = 0x0810) {
|
||||
writeShort(8) // subCommand
|
||||
writeShort(6) // count of TLVs, probably ignored by server?
|
||||
t8(2052)
|
||||
t104(client.t104)
|
||||
t116(client.miscBitMap, client.subSigMap)
|
||||
t174(EMPTY_BYTE_ARRAY)
|
||||
t17a(9)
|
||||
t197(byteArrayOf(0.toByte()))
|
||||
//t401(md5(client.device.guid + "12 34567890123456".toByteArray() + t402))
|
||||
//t19e(0)//==tlv408
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check SMS Login
|
||||
*/
|
||||
@ -104,6 +146,10 @@ internal class WtLogin {
|
||||
override fun toString(): String = "LoginPacketResponse.Success"
|
||||
}
|
||||
|
||||
class SmsRequestSuccess(override val bot: Bot) : LoginPacketResponse() {
|
||||
override fun toString(): String = "LoginPacketResponse.SmsRequestSuccess"
|
||||
}
|
||||
|
||||
data class Error(
|
||||
override val bot: Bot,
|
||||
val code: Int,
|
||||
@ -130,31 +176,25 @@ internal class WtLogin {
|
||||
}
|
||||
}
|
||||
|
||||
data class UnsafeLogin(
|
||||
class VerificationNeeded(
|
||||
override val bot: Bot,
|
||||
val url: String,
|
||||
) : LoginPacketResponse()
|
||||
|
||||
class SMSVerifyCodeNeeded(
|
||||
override val bot: Bot,
|
||||
val t402: ByteArray,
|
||||
val t403: ByteArray,
|
||||
val message: String?,
|
||||
val requests: DeviceVerificationRequests,
|
||||
) : LoginPacketResponse() {
|
||||
override fun toString(): String {
|
||||
return "LoginPacketResponse.SMSVerifyCodeNeeded(t402=${t402.toUHexString()}, t403=${t403.toUHexString()})"
|
||||
}
|
||||
override fun toString(): String =
|
||||
"LoginPacketResponse.VerificationNeeded(requests=$requests)"
|
||||
}
|
||||
|
||||
class DeviceLockLogin(
|
||||
override val bot: Bot,
|
||||
) : LoginPacketResponse() {
|
||||
override fun toString(): String = "WtLogin.Login.LoginPacketResponse.DeviceLockLogin"
|
||||
override fun toString(): String = "LoginPacketResponse.DeviceLockLogin"
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): LoginPacketResponse {
|
||||
|
||||
val subCommand = readUShort().toInt() // subCommand
|
||||
val subCommand = readUShort() // subCommand
|
||||
// println("subCommand=$subCommand")
|
||||
val type = readUByte()
|
||||
// println("type=$type")
|
||||
@ -177,10 +217,23 @@ internal class WtLogin {
|
||||
// writeFully(tlvMap[0x402] ?: EMPTY_BYTE_ARRAY)
|
||||
// }.readBytes().md5()
|
||||
// }
|
||||
|
||||
tlvMap[0x402]?.let { t402 ->
|
||||
bot.client.dpwd = getRandomByteArray(16)
|
||||
bot.client.t402 = t402
|
||||
bot.run {
|
||||
// client.dpwd = getRandomString(16).toByteArray()
|
||||
client.G = (client.device.guid + client.dpwd + t402).md5()
|
||||
}
|
||||
}
|
||||
|
||||
return when (type.toInt()) {
|
||||
0 -> onLoginSuccess(subCommand, tlvMap, bot)
|
||||
0 -> onLoginSuccess(subCommand.toInt(), tlvMap, bot)
|
||||
2 -> onSolveLoginCaptcha(tlvMap, bot)
|
||||
160, 239 /*-96*/ -> onUnsafeDeviceLogin(tlvMap, bot)
|
||||
160, 239 /*-96*/ -> onVerificationNeeded(subCommand.toShort(), tlvMap, bot)
|
||||
// 40: blocked
|
||||
// 161: 今日操作次数过多,请等待一天后再试。 (SMS)
|
||||
// 162: 可能也是 SMS 太频繁
|
||||
204 /*-52*/ -> onDevLockLogin(tlvMap, bot)
|
||||
// 1, 15 -> onErrorMessage(tlvMap) ?: error("Cannot find error message")
|
||||
else -> {
|
||||
@ -190,22 +243,65 @@ internal class WtLogin {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun onDevLockLogin(
|
||||
tlvMap: TlvMap,
|
||||
bot: QQAndroidBot
|
||||
): LoginPacketResponse.DeviceLockLogin {
|
||||
bot.client.t104 = tlvMap.getOrFail(0x104)
|
||||
bot.run {
|
||||
// client.dpwd = getRandomString(16).toByteArray()
|
||||
client.G = (client.device.guid + client.dpwd + tlvMap.getOrFail(0x402)).md5()
|
||||
}
|
||||
// println("403: " + tlvMap[0x403]?.toUHexString())
|
||||
return LoginPacketResponse.DeviceLockLogin(bot)
|
||||
}
|
||||
|
||||
private fun onUnsafeDeviceLogin(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.UnsafeLogin {
|
||||
return LoginPacketResponse.UnsafeLogin(bot, tlvMap.getOrFail(0x204).decodeToString())
|
||||
private fun onVerificationNeeded(subCommand: Short, tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse {
|
||||
val t174 = tlvMap[0x174]
|
||||
val t17b = tlvMap[0x17b]
|
||||
val message = tlvMap[0x17e]?.decodeToString()
|
||||
|
||||
t174?.let {
|
||||
bot.client.t174 = it
|
||||
}
|
||||
|
||||
if (t174 != null || t17b != null) {
|
||||
tlvMap[0x104]?.let {
|
||||
// verify token
|
||||
bot.client.t104 = it
|
||||
}
|
||||
}
|
||||
|
||||
tlvMap[0x403]?.let {
|
||||
bot.client.randSeed = it
|
||||
}
|
||||
|
||||
if (subCommand == WtLogin8.subCommand) {
|
||||
// response of submit sms
|
||||
// tlvMap 只有 0x17b 和 0x174
|
||||
return LoginPacketResponse.SmsRequestSuccess(bot)
|
||||
}
|
||||
var countryCode: String? = null
|
||||
var phoneNumber: String? = null
|
||||
tlvMap[0x178]?.read {
|
||||
// phone number
|
||||
countryCode = readUShortLVString()
|
||||
phoneNumber = readUShortLVString()
|
||||
}
|
||||
val url = tlvMap[0x204]?.decodeToString()
|
||||
check(url != null || t174 != null) {
|
||||
"Verification is needed but no method available."
|
||||
}
|
||||
return LoginPacketResponse.VerificationNeeded(
|
||||
bot,
|
||||
message = message,
|
||||
requests = object : DeviceVerificationRequests {
|
||||
override val sms: DeviceVerificationRequests.SmsRequest? =
|
||||
t174?.let { SmsVerifyInfo(countryCode, phoneNumber, t174, bot) }
|
||||
override val fallback: DeviceVerificationRequests.FallbackRequest? =
|
||||
url?.let { FallbackRequestImpl(it) }
|
||||
override val preferSms: Boolean = tlvMap[0x17b] != null
|
||||
override fun toString(): String {
|
||||
return "DeviceVerificationRequests(sms=$sms, preferSms=$preferSms, fallback=$fallback)"
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun onSolveLoginCaptcha(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Captcha {
|
||||
|
@ -27,7 +27,9 @@ internal object WtLogin10 : WtLoginExt {
|
||||
client: QQAndroidClient,
|
||||
subAppId: Long = 100,
|
||||
mainSigMap: Int = client.mainSigMap
|
||||
) = WtLogin.ExchangeEmp.buildLoginOutgoingPacket(client, bodyType = 2, key = ByteArray(16)) { sequenceId ->
|
||||
) = WtLogin.ExchangeEmp.buildLoginOutgoingPacket(
|
||||
client, bodyType = 2, key = ByteArray(16), remark = "10:fast-login"
|
||||
) { sequenceId ->
|
||||
writeSsoPacket(
|
||||
client,
|
||||
client.subAppId,
|
||||
|
@ -24,7 +24,9 @@ internal object WtLogin15 : WtLoginExt {
|
||||
|
||||
operator fun invoke(
|
||||
client: QQAndroidClient,
|
||||
) = WtLogin.ExchangeEmp.buildOutgoingUniPacket(client, bodyType = 2, key = ByteArray(16)) {
|
||||
) = WtLogin.ExchangeEmp.buildOutgoingUniPacket(
|
||||
client, bodyType = 2, key = ByteArray(16), remark = "15:refresh-keys"
|
||||
) {
|
||||
// writeSsoPacket(client, client.subAppId, WtLogin.ExchangeEmp.commandName, sequenceId = sequenceId) {
|
||||
writeOicqRequestPacket(
|
||||
client,
|
||||
|
@ -22,7 +22,7 @@ internal object WtLogin2 : WtLoginExt {
|
||||
fun SubmitSliderCaptcha(
|
||||
client: QQAndroidClient,
|
||||
ticket: String
|
||||
) = WtLogin.Login.buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
|
||||
) = WtLogin.Login.buildLoginOutgoingPacket(client, bodyType = 2, remark = "2:submit-slider") { sequenceId ->
|
||||
writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
|
||||
writeOicqRequestPacket(client, commandId = 0x0810) {
|
||||
writeShort(2) // subCommand
|
||||
@ -39,7 +39,7 @@ internal object WtLogin2 : WtLoginExt {
|
||||
client: QQAndroidClient,
|
||||
captchaSign: ByteArray,
|
||||
captchaAnswer: String
|
||||
) = WtLogin.Login.buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
|
||||
) = WtLogin.Login.buildLoginOutgoingPacket(client, bodyType = 2, remark = "2:submit-captcha") { sequenceId ->
|
||||
writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
|
||||
writeOicqRequestPacket(client, commandId = 0x0810) {
|
||||
writeShort(2) // subCommand
|
||||
|
@ -20,7 +20,7 @@ import net.mamoe.mirai.internal.network.subSigMap
|
||||
internal object WtLogin20 : WtLoginExt {
|
||||
operator fun invoke(
|
||||
client: QQAndroidClient
|
||||
) = WtLogin.Login.buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
|
||||
) = WtLogin.Login.buildLoginOutgoingPacket(client, bodyType = 2, remark = "20:dev-lock") { sequenceId ->
|
||||
writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
|
||||
writeOicqRequestPacket(client, commandId = 0x0810) {
|
||||
writeShort(20) // subCommand
|
||||
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin
|
||||
|
||||
import io.ktor.utils.io.core.*
|
||||
import net.mamoe.mirai.internal.network.QQAndroidClient
|
||||
import net.mamoe.mirai.internal.network.miscBitMap
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.*
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
|
||||
import net.mamoe.mirai.internal.network.subAppId
|
||||
import net.mamoe.mirai.internal.network.subSigMap
|
||||
import net.mamoe.mirai.utils.DeviceVerificationRequests
|
||||
|
||||
/**
|
||||
* Submit SMS.
|
||||
* @see DeviceVerificationRequests.SmsRequest.requestSms
|
||||
*/
|
||||
internal object WtLogin7 : WtLoginExt {
|
||||
operator fun invoke(
|
||||
client: QQAndroidClient,
|
||||
t174: ByteArray,
|
||||
code: String
|
||||
) = WtLogin.Login.buildLoginOutgoingPacket(
|
||||
client, bodyType = 2, remark = "7:submit-sms"
|
||||
) { sequenceId ->
|
||||
writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
|
||||
writeOicqRequestPacket(client, commandId = 0x0810) {
|
||||
writeShort(7) // subCommand
|
||||
writeShort(7) // count of TLVs
|
||||
|
||||
t8(2052)
|
||||
t104(client.t104)
|
||||
t116(client.miscBitMap, client.subSigMap)
|
||||
t174(client.t174 ?: t174)
|
||||
t17c(code.encodeToByteArray())
|
||||
t401(client.G)
|
||||
t198()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin
|
||||
|
||||
import io.ktor.utils.io.core.*
|
||||
import net.mamoe.mirai.internal.network.QQAndroidClient
|
||||
import net.mamoe.mirai.internal.network.miscBitMap
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.*
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
|
||||
import net.mamoe.mirai.internal.network.subAppId
|
||||
import net.mamoe.mirai.internal.network.subSigMap
|
||||
import net.mamoe.mirai.utils.DeviceVerificationRequests
|
||||
|
||||
/**
|
||||
* Request SMS.
|
||||
* @see DeviceVerificationRequests.SmsRequest.requestSms
|
||||
*/
|
||||
internal object WtLogin8 : WtLoginExt {
|
||||
val subCommand: Short = 8
|
||||
operator fun invoke(
|
||||
client: QQAndroidClient,
|
||||
t174: ByteArray
|
||||
) = WtLogin.Login.buildLoginOutgoingPacket(
|
||||
client, bodyType = 2, remark = "8:request-sms"
|
||||
) { sequenceId ->
|
||||
writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
|
||||
writeOicqRequestPacket(client, commandId = 0x0810) {
|
||||
writeShort(subCommand) // subCommand
|
||||
writeShort(6) // count of TLVs
|
||||
|
||||
t8(2052)
|
||||
t104(client.t104)
|
||||
t116(client.miscBitMap, client.subSigMap)
|
||||
t174(t174)
|
||||
t17a(9)
|
||||
t197()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -20,7 +20,9 @@ internal object WtLogin9 : WtLoginExt {
|
||||
operator fun invoke(
|
||||
client: QQAndroidClient,
|
||||
allowSlider: Boolean
|
||||
) = WtLogin.Login.buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
|
||||
) = WtLogin.Login.buildLoginOutgoingPacket(
|
||||
client, bodyType = 2, remark = "9:password-login"
|
||||
) { sequenceId ->
|
||||
writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
|
||||
writeOicqRequestPacket(client, commandId = 0x0810) {
|
||||
writeShort(9) // subCommand
|
||||
|
@ -38,6 +38,9 @@ internal inline fun WtLoginExt.analysisTlv0x531(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see WtLogin
|
||||
*/
|
||||
internal interface WtLoginExt { // so as not to register to global extension
|
||||
|
||||
fun onErrorMessage(type: Int, tlvMap: TlvMap, bot: QQAndroidBot): WtLogin.Login.LoginPacketResponse.Error? {
|
||||
|
Loading…
Reference in New Issue
Block a user