mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-23 22:00:10 +08:00
commit
ec140a0df2
File diff suppressed because it is too large
Load Diff
28
binary-compatibility-validator/android/build.gradle.kts
Normal file
28
binary-compatibility-validator/android/build.gradle.kts
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2019-2021 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/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
kotlin("plugin.serialization")
|
||||
|
||||
id("kotlinx-atomicfu")
|
||||
id("net.mamoe.kotlin-jvm-blocking-bridge")
|
||||
}
|
||||
|
||||
description = "Mirai API binary compatibility validator"
|
||||
|
||||
tasks.withType(kotlinx.validation.KotlinApiBuildTask::class) {
|
||||
inputClassesDirs =
|
||||
files(inputClassesDirs.files, project(":mirai-core-api").buildDir.resolve("classes/kotlin/android/main"))
|
||||
}
|
||||
|
||||
// tasks["apiDump"].dependsOn(project(":mirai-core-api").tasks["build"])
|
||||
// this dependency is set in mirai-core-api since binary validator is configured before mirai-core-api
|
@ -5918,7 +5918,7 @@ public final class net/mamoe/mirai/utils/SimpleLogger$LogPriority : java/lang/En
|
||||
public static fun values ()[Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/utils/SingleFileLogger : net/mamoe/mirai/utils/PlatformLogger {
|
||||
public final class net/mamoe/mirai/utils/SingleFileLogger : net/mamoe/mirai/utils/PlatformLogger, net/mamoe/mirai/utils/MiraiLogger {
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
public fun <init> (Ljava/lang/String;Ljava/io/File;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/io/File;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
|
@ -9,15 +9,6 @@
|
||||
|
||||
@file:Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
|
||||
|
||||
/*
|
||||
* Copyright 2019-2021 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/master/LICENSE
|
||||
*/
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
kotlin("plugin.serialization")
|
||||
|
212
build.gradle.kts
212
build.gradle.kts
@ -10,11 +10,8 @@
|
||||
@file:Suppress("UnstableApiUsage", "UNUSED_VARIABLE", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
|
||||
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
import org.jetbrains.kotlin.gradle.dsl.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
|
||||
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
@ -35,7 +32,7 @@ buildscript {
|
||||
}
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version Versions.kotlinCompiler
|
||||
kotlin("jvm") // version Versions.kotlinCompiler
|
||||
kotlin("plugin.serialization") version Versions.kotlinCompiler
|
||||
// id("org.jetbrains.dokka") version Versions.dokka
|
||||
id("net.mamoe.kotlin-jvm-blocking-bridge") version Versions.blockingBridge
|
||||
@ -51,7 +48,8 @@ configure<kotlinx.validation.ApiValidationExtension> {
|
||||
ignoredProjects.add(subproject.name)
|
||||
}
|
||||
ignoredProjects.remove("binary-compatibility-validator")
|
||||
// Enable validator for module `binary-compatibility-validator` only.
|
||||
ignoredProjects.remove("binary-compatibility-validator-android")
|
||||
// Enable validator for module `binary-compatibility-validator` and `-android` only.
|
||||
|
||||
|
||||
ignoredPackages.add("net.mamoe.mirai.internal")
|
||||
@ -62,7 +60,6 @@ configure<kotlinx.validation.ApiValidationExtension> {
|
||||
nonPublicMarkers.add("net.mamoe.mirai.MiraiExperimentalApi")
|
||||
}
|
||||
|
||||
project.ext.set("isAndroidSDKAvailable", false)
|
||||
GpgSigner.setup(project)
|
||||
|
||||
tasks.register("publishMiraiCoreArtifactsToMavenLocal") {
|
||||
@ -74,24 +71,6 @@ tasks.register("publishMiraiCoreArtifactsToMavenLocal") {
|
||||
)
|
||||
}
|
||||
|
||||
// until
|
||||
// https://youtrack.jetbrains.com/issue/KT-37152,
|
||||
// are fixed.
|
||||
|
||||
/*
|
||||
runCatching {
|
||||
val keyProps = Properties().apply {
|
||||
file("local.properties").takeIf { it.exists() }?.inputStream()?.use { load(it) }
|
||||
}
|
||||
if (keyProps.getProperty("sdk.dir", "").isNotEmpty()) {
|
||||
project.ext.set("isAndroidSDKAvailable", true)
|
||||
} else {
|
||||
project.ext.set("isAndroidSDKAvailable", false)
|
||||
}
|
||||
}.exceptionOrNull()?.run {
|
||||
project.ext.set("isAndroidSDKAvailable", false)
|
||||
}*/
|
||||
|
||||
allprojects {
|
||||
group = "net.mamoe"
|
||||
version = Versions.project
|
||||
@ -111,7 +90,6 @@ allprojects {
|
||||
configureMppShadow()
|
||||
configureEncoding()
|
||||
configureKotlinTestSettings()
|
||||
configureKotlinCompilerSettings()
|
||||
configureKotlinExperimentalUsages()
|
||||
|
||||
runCatching {
|
||||
@ -192,52 +170,34 @@ fun Project.configureDokka() {
|
||||
// }
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE") // or error
|
||||
fun Project.configureJvmTarget() {
|
||||
tasks.withType(KotlinJvmCompile::class.java) {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
kotlinTargets.orEmpty().filterIsInstance<KotlinJvmTarget>().forEach { target ->
|
||||
target.compilations.all {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
kotlinOptions.languageVersion = "1.4"
|
||||
}
|
||||
target.testRuns["test"].executionTask.configure { useJUnitPlatform() }
|
||||
}
|
||||
|
||||
extensions.findByType(JavaPluginExtension::class.java)?.run {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.configureMppShadow() {
|
||||
val kotlin =
|
||||
runCatching {
|
||||
|
||||
(this as ExtensionAware).extensions.getByName("kotlin") as? KotlinMultiplatformExtension
|
||||
}.getOrNull() ?: return
|
||||
|
||||
val shadowJvmJar by tasks.creating(ShadowJar::class) sd@{
|
||||
group = "mirai"
|
||||
archiveClassifier.set("-all")
|
||||
if (project.configurations.findByName("jvmRuntimeClasspath") != null) {
|
||||
val shadowJvmJar by tasks.creating(ShadowJar::class) sd@{
|
||||
group = "mirai"
|
||||
archiveClassifier.set("-all")
|
||||
|
||||
val compilations =
|
||||
kotlin.targets.filter { it.platformType == KotlinPlatformType.jvm }
|
||||
.map { it.compilations["main"] }
|
||||
val compilations =
|
||||
kotlin.targets.filter { it.platformType == KotlinPlatformType.jvm }
|
||||
.map { it.compilations["main"] }
|
||||
|
||||
compilations.forEach {
|
||||
dependsOn(it.compileKotlinTask)
|
||||
from(it.output)
|
||||
}
|
||||
compilations.forEach {
|
||||
dependsOn(it.compileKotlinTask)
|
||||
from(it.output)
|
||||
}
|
||||
|
||||
from(project.configurations.getByName("jvmRuntimeClasspath"))
|
||||
from(project.configurations.findByName("jvmRuntimeClasspath"))
|
||||
|
||||
this.exclude { file ->
|
||||
file.name.endsWith(".sf", ignoreCase = true)
|
||||
}
|
||||
this.exclude { file ->
|
||||
file.name.endsWith(".sf", ignoreCase = true)
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
this.manifest {
|
||||
this.attributes(
|
||||
"Manifest-Version" to 1,
|
||||
@ -246,109 +206,39 @@ fun Project.configureMppShadow() {
|
||||
"Implementation-Version" to this.version.toString()
|
||||
)
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.configureEncoding() {
|
||||
tasks.withType(JavaCompile::class.java) {
|
||||
options.encoding = "UTF8"
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.configureKotlinTestSettings() {
|
||||
tasks.withType(Test::class) {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
when {
|
||||
isKotlinJvmProject -> {
|
||||
dependencies {
|
||||
testImplementation(kotlin("test-junit5"))
|
||||
|
||||
testApi("org.junit.jupiter:junit-jupiter-api:5.2.0")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0")
|
||||
}
|
||||
}
|
||||
isKotlinMpp -> {
|
||||
kotlinSourceSets?.forEach { sourceSet ->
|
||||
if (sourceSet.name.endsWith("test", ignoreCase = true)) {
|
||||
sourceSet.dependencies {
|
||||
api(kotlin("test-junit5"))
|
||||
api("org.junit.jupiter:junit-jupiter-api:5.2.0")
|
||||
runtimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0")
|
||||
}
|
||||
|
||||
fun Project.configureEncoding() {
|
||||
tasks.withType(JavaCompile::class.java) {
|
||||
options.encoding = "UTF8"
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.configureKotlinTestSettings() {
|
||||
tasks.withType(Test::class) {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
when {
|
||||
isKotlinJvmProject -> {
|
||||
dependencies {
|
||||
testImplementation(kotlin("test-junit5"))
|
||||
|
||||
testApi("org.junit.jupiter:junit-jupiter-api:5.2.0")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0")
|
||||
}
|
||||
}
|
||||
isKotlinMpp -> {
|
||||
kotlinSourceSets?.forEach { sourceSet ->
|
||||
if (sourceSet.name.endsWith("test", ignoreCase = true)) {
|
||||
sourceSet.dependencies {
|
||||
api(kotlin("test-junit5"))
|
||||
api("org.junit.jupiter:junit-jupiter-api:5.2.0")
|
||||
runtimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.configureKotlinCompilerSettings() {
|
||||
val kotlinCompilations = kotlinCompilations ?: return
|
||||
for (kotlinCompilation in kotlinCompilations) with(kotlinCompilation) {
|
||||
if (isKotlinJvmProject) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
this as KotlinCompilation<KotlinJvmOptions>
|
||||
}
|
||||
kotlinOptions.freeCompilerArgs += "-Xjvm-default=all"
|
||||
}
|
||||
}
|
||||
|
||||
val experimentalAnnotations = arrayOf(
|
||||
"kotlin.RequiresOptIn",
|
||||
"kotlin.contracts.ExperimentalContracts",
|
||||
"kotlin.experimental.ExperimentalTypeInference",
|
||||
"kotlin.ExperimentalUnsignedTypes",
|
||||
"kotlin.time.ExperimentalTime",
|
||||
"kotlin.io.path.ExperimentalPathApi",
|
||||
"io.ktor.util.KtorExperimentalAPI",
|
||||
|
||||
"kotlinx.serialization.ExperimentalSerializationApi",
|
||||
|
||||
"net.mamoe.mirai.utils.MiraiInternalApi",
|
||||
"net.mamoe.mirai.utils.MiraiExperimentalApi",
|
||||
"net.mamoe.mirai.LowLevelApi",
|
||||
"net.mamoe.mirai.utils.UnstableExternalImage",
|
||||
|
||||
"net.mamoe.mirai.message.data.ExperimentalMessageKey",
|
||||
"net.mamoe.mirai.console.ConsoleFrontEndImplementation",
|
||||
"net.mamoe.mirai.console.util.ConsoleInternalApi",
|
||||
"net.mamoe.mirai.console.util.ConsoleExperimentalApi"
|
||||
)
|
||||
|
||||
fun Project.configureKotlinExperimentalUsages() {
|
||||
val sourceSets = kotlinSourceSets ?: return
|
||||
|
||||
for (target in sourceSets) {
|
||||
target.languageSettings.progressiveMode = true
|
||||
target.languageSettings.enableLanguageFeature("InlineClasses")
|
||||
experimentalAnnotations.forEach { a ->
|
||||
target.languageSettings.useExperimentalAnnotation(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.configureFlattenSourceSets() {
|
||||
sourceSets {
|
||||
findByName("main")?.apply {
|
||||
resources.setSrcDirs(listOf(projectDir.resolve("resources")))
|
||||
java.setSrcDirs(listOf(projectDir.resolve("src")))
|
||||
}
|
||||
findByName("test")?.apply {
|
||||
resources.setSrcDirs(listOf(projectDir.resolve("resources")))
|
||||
java.setSrcDirs(listOf(projectDir.resolve("test")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Project.kotlinSourceSets get() = extensions.findByName("kotlin").safeAs<KotlinProjectExtension>()?.sourceSets
|
||||
|
||||
val Project.kotlinTargets
|
||||
get() =
|
||||
extensions.findByName("kotlin").safeAs<KotlinSingleTargetExtension>()?.target?.let { listOf(it) }
|
||||
?: extensions.findByName("kotlin").safeAs<KotlinMultiplatformExtension>()?.targets
|
||||
|
||||
val Project.isKotlinJvmProject: Boolean get() = extensions.findByName("kotlin") is KotlinJvmProjectExtension
|
||||
val Project.isKotlinMpp: Boolean get() = extensions.findByName("kotlin") is KotlinMultiplatformExtension
|
||||
|
||||
val Project.kotlinCompilations
|
||||
get() = kotlinTargets?.flatMap { it.compilations }
|
||||
}
|
@ -14,6 +14,8 @@ plugins {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
@ -39,6 +41,9 @@ fun version(name: String): String {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
val asmVersion = version("asm")
|
||||
fun asm(module: String) = "org.ow2.asm:asm-$module:$asmVersion"
|
||||
|
||||
fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version"
|
||||
fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version"
|
||||
|
||||
@ -46,6 +51,12 @@ dependencies {
|
||||
|
||||
api("com.jfrog.bintray.gradle", "gradle-bintray-plugin", version("bintray"))
|
||||
api("com.github.jengelman.gradle.plugins", "shadow", version("shadow"))
|
||||
api("org.jetbrains.kotlin", "kotlin-gradle-plugin", version("kotlinCompiler"))
|
||||
api("org.jetbrains.kotlin", "kotlin-compiler-embeddable", version("kotlinCompiler"))
|
||||
api("com.android.tools.build", "gradle", version("androidGradlePlugin"))
|
||||
api(asm("tree"))
|
||||
api(asm("util"))
|
||||
api(asm("commons"))
|
||||
|
||||
api(gradleApi())
|
||||
}
|
@ -10,7 +10,7 @@
|
||||
import org.gradle.api.NamedDomainObjectCollection
|
||||
import org.gradle.api.NamedDomainObjectProvider
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.provideDelegate
|
||||
import java.util.*
|
||||
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
@ -21,11 +21,33 @@ import org.gradle.kotlin.dsl.provideDelegate
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
val Project.isAndroidSDKAvailable: Boolean
|
||||
get() {
|
||||
val isAndroidSDKAvailable: Boolean by this
|
||||
return isAndroidSDKAvailable
|
||||
private object ProjectAndroidSdkAvailability {
|
||||
val map: MutableMap<String, Boolean> = mutableMapOf()
|
||||
|
||||
@Suppress("UNUSED_PARAMETER", "UNREACHABLE_CODE")
|
||||
@Synchronized
|
||||
operator fun get(project: Project): Boolean {
|
||||
return true
|
||||
if (map[project.path] != null) return map[project.path]!!
|
||||
|
||||
val projectAvailable = project.runCatching {
|
||||
val keyProps = Properties().apply {
|
||||
file("local.properties").takeIf { it.exists() }?.inputStream()?.use { load(it) }
|
||||
}
|
||||
keyProps.getProperty("sdk.dir", "").isNotEmpty()
|
||||
}.getOrElse { false }
|
||||
|
||||
|
||||
fun impl(): Boolean {
|
||||
if (project === project.rootProject) return projectAvailable
|
||||
return projectAvailable || get(project.rootProject)
|
||||
}
|
||||
map[project.path] = impl()
|
||||
return map[project.path]!!
|
||||
}
|
||||
}
|
||||
|
||||
val Project.isAndroidSDKAvailable: Boolean get() = ProjectAndroidSdkAvailability[this]
|
||||
|
||||
val <T> NamedDomainObjectCollection<T>.androidMain: NamedDomainObjectProvider<T>
|
||||
get() = named("androidMain")
|
||||
@ -43,16 +65,12 @@ val <T> NamedDomainObjectCollection<T>.commonMain: NamedDomainObjectProvider<T>
|
||||
get() = named("commonMain")
|
||||
|
||||
fun Project.printAndroidNotInstalled() {
|
||||
// println(
|
||||
// """Android SDK 可能未安装.
|
||||
// $name 的 Android 目标编译将不会进行.
|
||||
// 这不会影响 Android 以外的平台的编译.
|
||||
// """.trimIndent()
|
||||
// )
|
||||
// println(
|
||||
// """Android SDK might not be installed.
|
||||
// Android target of $name will not be compiled.
|
||||
// It does no influence on the compilation of other platforms.
|
||||
// """.trimIndent()
|
||||
// )
|
||||
println(
|
||||
"""Android SDK 可能未安装. $name 的 Android 目标编译将不会进行. 这不会影响 Android 以外的平台的编译.
|
||||
""".trimIndent()
|
||||
)
|
||||
println(
|
||||
"""Android SDK might not be installed. Android target of $name will not be compiled. It does no influence on the compilation of other platforms.
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
168
buildSrc/src/main/kotlin/ProjectConfigure.kt
Normal file
168
buildSrc/src/main/kotlin/ProjectConfigure.kt
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2019-2021 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/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
|
||||
|
||||
import org.gradle.api.JavaVersion
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.plugins.JavaPluginExtension
|
||||
import org.gradle.api.tasks.compile.JavaCompile
|
||||
import org.gradle.api.tasks.testing.Test
|
||||
import org.gradle.kotlin.dsl.*
|
||||
import org.jetbrains.kotlin.gradle.dsl.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
|
||||
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
|
||||
|
||||
|
||||
fun Project.useIr() {
|
||||
kotlinCompilations?.forEach { kotlinCompilation ->
|
||||
kotlinCompilation.kotlinOptions.freeCompilerArgs += "-Xuse-ir"
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.configureJvmTarget() {
|
||||
val defaultVer = JavaVersion.VERSION_1_8
|
||||
|
||||
tasks.withType(KotlinJvmCompile::class.java) {
|
||||
kotlinOptions.languageVersion = "1.4"
|
||||
kotlinOptions.jvmTarget = defaultVer.toString()
|
||||
kotlinOptions.freeCompilerArgs += "-Xjvm-default=all"
|
||||
}
|
||||
|
||||
extensions.findByType(JavaPluginExtension::class.java)?.run {
|
||||
sourceCompatibility = defaultVer
|
||||
targetCompatibility = defaultVer
|
||||
}
|
||||
|
||||
kotlinTargets.orEmpty().filterIsInstance<KotlinJvmTarget>().forEach { target ->
|
||||
when (target.attributes.getAttribute(KotlinPlatformType.attribute)) { // mirai does magic, don't use target.platformType
|
||||
KotlinPlatformType.androidJvm -> {
|
||||
target.compilations.all {
|
||||
/*
|
||||
* Kotlin JVM compiler generates Long.hashCode witch is available since API 26 when targeting JVM 1.8 while IR prefer member function hashCode always.
|
||||
*/
|
||||
// kotlinOptions.useIR = true
|
||||
|
||||
// IR cannot compile mirai. We'll wait for Kotlin 1.5 for stable IR release.
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
target.testRuns["test"].executionTask.configure { useJUnitPlatform() }
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.configureEncoding() {
|
||||
tasks.withType(JavaCompile::class.java) {
|
||||
options.encoding = "UTF8"
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.configureKotlinTestSettings() {
|
||||
tasks.withType(Test::class) {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
when {
|
||||
isKotlinJvmProject -> {
|
||||
dependencies {
|
||||
"testImplementation"(kotlin("test-junit5"))
|
||||
|
||||
"testApi"("org.junit.jupiter:junit-jupiter-api:5.2.0")
|
||||
"testRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:5.2.0")
|
||||
}
|
||||
}
|
||||
isKotlinMpp -> {
|
||||
kotlinSourceSets?.forEach { sourceSet ->
|
||||
if (sourceSet.name == "common") {
|
||||
sourceSet.dependencies {
|
||||
implementation(kotlin("test"))
|
||||
implementation(kotlin("test-annotations-common"))
|
||||
}
|
||||
} else {
|
||||
sourceSet.dependencies {
|
||||
implementation(kotlin("test-junit5"))
|
||||
|
||||
implementation("org.junit.jupiter:junit-jupiter-api:5.2.0")
|
||||
implementation("org.junit.jupiter:junit-jupiter-engine:5.2.0")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val experimentalAnnotations = arrayOf(
|
||||
"kotlin.RequiresOptIn",
|
||||
"kotlin.contracts.ExperimentalContracts",
|
||||
"kotlin.experimental.ExperimentalTypeInference",
|
||||
"kotlin.ExperimentalUnsignedTypes",
|
||||
"kotlin.time.ExperimentalTime",
|
||||
"kotlin.io.path.ExperimentalPathApi",
|
||||
"io.ktor.util.KtorExperimentalAPI",
|
||||
|
||||
"kotlinx.serialization.ExperimentalSerializationApi",
|
||||
|
||||
"net.mamoe.mirai.utils.MiraiInternalApi",
|
||||
"net.mamoe.mirai.utils.MiraiExperimentalApi",
|
||||
"net.mamoe.mirai.LowLevelApi",
|
||||
"net.mamoe.mirai.utils.UnstableExternalImage",
|
||||
|
||||
"net.mamoe.mirai.message.data.ExperimentalMessageKey",
|
||||
"net.mamoe.mirai.console.ConsoleFrontEndImplementation",
|
||||
"net.mamoe.mirai.console.util.ConsoleInternalApi",
|
||||
"net.mamoe.mirai.console.util.ConsoleExperimentalApi"
|
||||
)
|
||||
|
||||
fun Project.configureKotlinExperimentalUsages() {
|
||||
val sourceSets = kotlinSourceSets ?: return
|
||||
|
||||
for (target in sourceSets) {
|
||||
target.configureKotlinExperimentalUsages()
|
||||
}
|
||||
}
|
||||
|
||||
fun KotlinSourceSet.configureKotlinExperimentalUsages() {
|
||||
languageSettings.progressiveMode = true
|
||||
languageSettings.enableLanguageFeature("InlineClasses")
|
||||
experimentalAnnotations.forEach { a ->
|
||||
languageSettings.useExperimentalAnnotation(a)
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.configureFlattenSourceSets() {
|
||||
sourceSets {
|
||||
findByName("main")?.apply {
|
||||
resources.setSrcDirs(listOf(projectDir.resolve("resources")))
|
||||
java.setSrcDirs(listOf(projectDir.resolve("src")))
|
||||
}
|
||||
findByName("test")?.apply {
|
||||
resources.setSrcDirs(listOf(projectDir.resolve("resources")))
|
||||
java.setSrcDirs(listOf(projectDir.resolve("test")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T> Any?.safeAs(): T? {
|
||||
return this as? T
|
||||
}
|
||||
|
||||
val Project.kotlinSourceSets get() = extensions.findByName("kotlin").safeAs<KotlinProjectExtension>()?.sourceSets
|
||||
|
||||
val Project.kotlinTargets
|
||||
get() =
|
||||
extensions.findByName("kotlin").safeAs<KotlinSingleTargetExtension>()?.target?.let { listOf(it) }
|
||||
?: extensions.findByName("kotlin").safeAs<KotlinMultiplatformExtension>()?.targets
|
||||
|
||||
val Project.isKotlinJvmProject: Boolean get() = extensions.findByName("kotlin") is KotlinJvmProjectExtension
|
||||
val Project.isKotlinMpp: Boolean get() = extensions.findByName("kotlin") is KotlinMultiplatformExtension
|
||||
|
||||
val Project.kotlinCompilations
|
||||
get() = kotlinTargets?.flatMap { it.compilations }
|
@ -12,7 +12,7 @@
|
||||
import org.gradle.api.attributes.Attribute
|
||||
|
||||
object Versions {
|
||||
const val project = "2.5.0-dev-2"
|
||||
const val project = "2.5.0-dev-android-1"
|
||||
|
||||
const val core = project
|
||||
const val console = project
|
||||
@ -34,13 +34,15 @@ object Versions {
|
||||
|
||||
const val blockingBridge = "1.10.0"
|
||||
|
||||
const val androidGradlePlugin = "3.5.3"
|
||||
const val androidGradlePlugin = "4.1.1"
|
||||
const val android = "4.1.1.4"
|
||||
|
||||
const val bintray = "1.8.5"
|
||||
const val shadow = "6.1.0"
|
||||
|
||||
const val slf4j = "1.7.30"
|
||||
const val log4j = "2.13.3"
|
||||
const val asm = "9.1"
|
||||
|
||||
|
||||
// If you the versions below, you need to sync changes to mirai-console/buildSrc/src/main/kotlin/Versions.kt
|
||||
@ -104,3 +106,5 @@ const val `jetbrains-annotations` = "org.jetbrains:annotations:19.0.0"
|
||||
|
||||
|
||||
const val `caller-finder` = "io.github.karlatemp:caller:1.1.1"
|
||||
|
||||
const val `android-runtime` = "com.google.android:android:${Versions.android}"
|
283
buildSrc/src/main/kotlin/androidutil/AndroidApiLevelCheck.kt
Normal file
283
buildSrc/src/main/kotlin/androidutil/AndroidApiLevelCheck.kt
Normal file
@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright 2019-2021 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/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package androidutil
|
||||
|
||||
import groovy.util.Node
|
||||
import groovy.util.XmlParser
|
||||
import org.gradle.api.Project
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.Type
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
import org.objectweb.asm.tree.FieldInsnNode
|
||||
import org.objectweb.asm.tree.MethodInsnNode
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
|
||||
object AndroidApiLevelCheck {
|
||||
data class ClassInfo(
|
||||
val name: String,
|
||||
val since: Int,
|
||||
val superTypes: List<SuperInfo>,
|
||||
val fieldInfos: Map<String, MemberInfo>,
|
||||
val methodInfos: Map<String, MemberInfo>
|
||||
) {
|
||||
data class SuperInfo(
|
||||
val name: String,
|
||||
val since: Int?,
|
||||
val removed: Int?
|
||||
)
|
||||
|
||||
data class MemberInfo(
|
||||
val name: String,
|
||||
val since: Int?
|
||||
)
|
||||
}
|
||||
|
||||
class Analyzer(
|
||||
val classesInfos: Map<String, ClassInfo>
|
||||
) {
|
||||
var path: String? = null
|
||||
var context: String? = null
|
||||
var file: File? = null
|
||||
var apilevel = 0
|
||||
var reported = false
|
||||
inline fun withPath(path: String, block: Analyzer.() -> Unit) {
|
||||
this.path = path
|
||||
block(this)
|
||||
this.path = null
|
||||
}
|
||||
|
||||
inline fun withContext(context: String, block: Analyzer.() -> Unit) {
|
||||
this.context = context
|
||||
block(this)
|
||||
this.context = null
|
||||
}
|
||||
|
||||
fun report(prefix: String, message: String) {
|
||||
reported = true
|
||||
file?.let { file ->
|
||||
println("> $file")
|
||||
this.file = null
|
||||
}
|
||||
context?.let { context ->
|
||||
println(" > $context")
|
||||
this.context = null
|
||||
}
|
||||
path?.let { path ->
|
||||
println(" > $path")
|
||||
this.path = null
|
||||
}
|
||||
if (prefix.isBlank()) {
|
||||
message
|
||||
} else {
|
||||
"$prefix: $message"
|
||||
}.split('\n').forEach { println(" $it") }
|
||||
}
|
||||
|
||||
fun needCheck(type: String): Boolean {
|
||||
if (type.startsWith("android/")) return true
|
||||
if (type.startsWith("androidx/")) return true
|
||||
if (type.startsWith("java/")) return true
|
||||
if (type.startsWith("javax/")) return true
|
||||
return classesInfos.containsKey(type)
|
||||
}
|
||||
|
||||
fun checkClass(prefix: String, name: String) {
|
||||
if (!needCheck(name)) return
|
||||
val info = classesInfos[name]
|
||||
if (info == null) {
|
||||
report(prefix, "$name not found in api-version.xml")
|
||||
return
|
||||
}
|
||||
if (info.since > apilevel) {
|
||||
report(prefix, "$name since api level ${info.since}")
|
||||
}
|
||||
}
|
||||
|
||||
fun checkFieldAccess(prefix: String, owner: String, name: String) {
|
||||
if (!needCheck(owner)) return
|
||||
|
||||
val info = classesInfos[owner] ?: return
|
||||
val field = info.fieldInfos[name]
|
||||
if (field == null) {
|
||||
report(prefix, "No field $owner.$name")
|
||||
return
|
||||
}
|
||||
if ((field.since ?: 0) > apilevel) {
|
||||
report(prefix, "$owner.$name since api level ${field.since}")
|
||||
}
|
||||
}
|
||||
|
||||
fun checkMethodAccess(prefix: String, owner: String, name: String) {
|
||||
if (!needCheck(owner)) return
|
||||
|
||||
fun findMethod(type: String): ClassInfo.MemberInfo? {
|
||||
val cinfo = classesInfos[type] ?: return null
|
||||
return cinfo.methodInfos[name] ?: kotlin.run {
|
||||
cinfo.superTypes.forEach { stype ->
|
||||
if (stype.removed != null) {
|
||||
if (apilevel >= stype.removed) return@forEach
|
||||
}
|
||||
if (stype.since != null) {
|
||||
if (apilevel < stype.since) return@forEach
|
||||
}
|
||||
findMethod(stype.name)?.let { return it }
|
||||
}
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val method = findMethod(owner)
|
||||
if (method == null) {
|
||||
report(prefix, "No method $owner.$name")
|
||||
return
|
||||
}
|
||||
if ((method.since ?: 0) > apilevel) {
|
||||
report(prefix, "$owner.$name since api level ${method.since}")
|
||||
}
|
||||
}
|
||||
|
||||
private val Type.top: Type
|
||||
get() = when (sort) {
|
||||
Type.ARRAY -> elementType
|
||||
else -> this
|
||||
}
|
||||
|
||||
|
||||
fun analyze(classNode: ClassNode, file: File) {
|
||||
this.file = file
|
||||
withContext("Check class") {
|
||||
withPath("class checking") {
|
||||
checkClass("Couldn't extend ${classNode.superName}", classNode.superName)
|
||||
classNode.interfaces?.forEach { checkClass("Couldn't implements $it", it) }
|
||||
}
|
||||
}
|
||||
classNode.fields?.forEach { field ->
|
||||
withContext("Field ${field.name}: ${field.desc}") {
|
||||
val type = Type.getType(field.desc).top.internalName
|
||||
checkClass("Couldn't access $type", type)
|
||||
}
|
||||
}
|
||||
classNode.methods?.forEach { method ->
|
||||
withContext("Method ${method.name}${method.desc}") {
|
||||
withPath("Checking method desc") {
|
||||
val returnType = Type.getReturnType(method.desc).top.internalName
|
||||
checkClass("Couldn't access $returnType", returnType)
|
||||
Type.getArgumentTypes(method.desc).map { it.top.internalName }.forEach {
|
||||
checkClass("Couldn't access $it", it)
|
||||
}
|
||||
}
|
||||
method.instructions?.forEach { insn ->
|
||||
when (insn) {
|
||||
is FieldInsnNode -> {
|
||||
withPath("Access field ${insn.owner}.${insn.name}: ${insn.desc}") {
|
||||
val type = Type.getType(insn.desc)
|
||||
val prefix = "Couldn't access ${insn.owner}.${insn.name}: ${insn.desc}"
|
||||
checkClass(prefix, type.internalName)
|
||||
checkFieldAccess(prefix, insn.owner, insn.name)
|
||||
}
|
||||
}
|
||||
is MethodInsnNode -> {
|
||||
withPath("Invoke method ${insn.owner}.${insn.name}${insn.desc}") {
|
||||
checkClass("Couldn't access ${insn.owner}", insn.owner)
|
||||
val returnType = Type.getReturnType(insn.desc).top.internalName
|
||||
checkClass("Couldn't access $returnType", returnType)
|
||||
Type.getArgumentTypes(insn.desc).map { it.top.internalName }.forEach {
|
||||
checkClass("Couldn't access $it", it)
|
||||
}
|
||||
checkMethodAccess(
|
||||
"Couldn't access ${insn.owner}.${insn.name}${insn.desc}",
|
||||
insn.owner,
|
||||
insn.name + insn.desc
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun check(classes: File, level: Int, project: Project) {
|
||||
val apiVersionsFile = project.rootProject.projectDir.resolve("buildSrc/src/main/resources/androidutil/api-versions.xml")
|
||||
val classesInfos = mutableMapOf<String, ClassInfo>()
|
||||
XmlParser().parse(apiVersionsFile).children().forEach { classNode ->
|
||||
classNode as Node
|
||||
if (classNode.name() == "class") {
|
||||
val fieldInfos = mutableMapOf<String, ClassInfo.MemberInfo>()
|
||||
val methodInfos = mutableMapOf<String, ClassInfo.MemberInfo>()
|
||||
val cinfo = ClassInfo(
|
||||
classNode.attribute("name").toString(),
|
||||
classNode.attribute("since").toString().toInt(),
|
||||
(classNode.children() as List<Node>).filter {
|
||||
it.name() == "implements" || it.name() == "extends"
|
||||
}.map {
|
||||
ClassInfo.SuperInfo(
|
||||
it.attribute("name").toString(),
|
||||
it.attribute("since")?.toString()?.toInt(),
|
||||
it.attribute("removed")?.toString()?.toInt()
|
||||
)
|
||||
},
|
||||
fieldInfos, methodInfos
|
||||
)
|
||||
classesInfos[cinfo.name] = cinfo
|
||||
classNode.children().forEach { memberNode ->
|
||||
memberNode as Node
|
||||
when (memberNode.name()) {
|
||||
"method" -> {
|
||||
val method = ClassInfo.MemberInfo(
|
||||
memberNode.attribute("name").toString(),
|
||||
memberNode.attribute("since")?.toString()?.toInt()
|
||||
)
|
||||
methodInfos[method.name] = method
|
||||
}
|
||||
"field" -> {
|
||||
val field = ClassInfo.MemberInfo(
|
||||
memberNode.attribute("name").toString(),
|
||||
memberNode.attribute("since")?.toString()?.toInt()
|
||||
)
|
||||
fieldInfos[field.name] = field
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val analyzer = Analyzer(classesInfos)
|
||||
analyzer.apilevel = level
|
||||
|
||||
classes.walk()
|
||||
.filter { it.isFile && it.extension == "class" }
|
||||
.map { file ->
|
||||
kotlin.runCatching {
|
||||
val cnode = ClassNode()
|
||||
file.inputStream().use {
|
||||
ClassReader(it).accept(cnode, 0)
|
||||
}
|
||||
cnode
|
||||
}.getOrNull() to file
|
||||
}
|
||||
.filter { it.first != null }
|
||||
.map {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
it as Pair<ClassNode, File>
|
||||
}
|
||||
.forEach { (classNode, file) ->
|
||||
analyzer.analyze(classNode, file)
|
||||
}
|
||||
|
||||
if (analyzer.reported) {
|
||||
error("Verify failed")
|
||||
}
|
||||
}
|
||||
}
|
82286
buildSrc/src/main/resources/androidutil/api-versions.xml
Normal file
82286
buildSrc/src/main/resources/androidutil/api-versions.xml
Normal file
File diff suppressed because it is too large
Load Diff
@ -20,3 +20,5 @@ kotlin.native.enableDependencyPropagation=false
|
||||
#kotlin.mpp.enableGranularSourceSetsMetadata=true
|
||||
systemProp.org.gradle.internal.publish.checksums.insecure=true
|
||||
gnsp.disableApplyOnlyOnRootProjectEnforcement=true
|
||||
# We may target 15 with Kotlin 1.5 IR
|
||||
mirai.android.target.api.level=24
|
||||
|
@ -12,7 +12,6 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
kotlin("plugin.serialization")
|
||||
id("java")
|
||||
`maven-publish`
|
||||
id("com.jfrog.bintray")
|
||||
id("net.mamoe.kotlin-jvm-blocking-bridge")
|
||||
|
@ -14,7 +14,7 @@ plugins {
|
||||
kotlin("multiplatform")
|
||||
kotlin("plugin.serialization")
|
||||
|
||||
id("kotlinx-atomicfu")
|
||||
//id("kotlinx-atomicfu")
|
||||
id("signing")
|
||||
id("net.mamoe.kotlin-jvm-blocking-bridge")
|
||||
|
||||
@ -28,9 +28,13 @@ kotlin {
|
||||
explicitApi()
|
||||
|
||||
if (isAndroidSDKAvailable) {
|
||||
apply(from = rootProject.file("gradle/android.gradle"))
|
||||
android("android") {
|
||||
publishAllLibraryVariants()
|
||||
// apply(from = rootProject.file("gradle/android.gradle"))
|
||||
// android("android") {
|
||||
// publishAllLibraryVariants()
|
||||
// }
|
||||
jvm("android") {
|
||||
attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.androidJvm)
|
||||
// publishAllLibraryVariants()
|
||||
}
|
||||
} else {
|
||||
printAndroidNotInstalled()
|
||||
@ -47,7 +51,7 @@ kotlin {
|
||||
// }
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
implementation(project(":mirai-core-utils"))
|
||||
api(kotlin("serialization"))
|
||||
@ -76,16 +80,20 @@ kotlin {
|
||||
}
|
||||
|
||||
if (isAndroidSDKAvailable) {
|
||||
androidMain {
|
||||
val androidMain by getting {
|
||||
dependsOn(commonMain)
|
||||
dependencies {
|
||||
compileOnly(`android-runtime`)
|
||||
api1(`ktor-client-android`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val jvmMain by getting
|
||||
val jvmMain by getting {
|
||||
|
||||
jvmTest {
|
||||
}
|
||||
|
||||
val jvmTest by getting {
|
||||
dependencies {
|
||||
runtimeOnly(files("build/classes/kotlin/jvm/test")) // classpath is not properly set by IDE
|
||||
}
|
||||
@ -93,6 +101,18 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("checkAndroidApiLevel") {
|
||||
doFirst {
|
||||
androidutil.AndroidApiLevelCheck.check(
|
||||
buildDir.resolve("classes/kotlin/android/main"),
|
||||
project.property("mirai.android.target.api.level")!!.toString().toInt(),
|
||||
project
|
||||
)
|
||||
}
|
||||
group = "verification"
|
||||
this.mustRunAfter("androidMainClasses")
|
||||
}
|
||||
tasks.getByName("androidTest").dependsOn("checkAndroidApiLevel")
|
||||
|
||||
fun org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler.implementation1(dependencyNotation: String) =
|
||||
implementation(dependencyNotation) {
|
||||
@ -116,4 +136,5 @@ configureMppPublishing()
|
||||
|
||||
afterEvaluate {
|
||||
project(":binary-compatibility-validator").tasks["apiBuild"].dependsOn(project(":mirai-core-api").tasks["build"])
|
||||
project(":binary-compatibility-validator-android").tasks["apiBuild"].dependsOn(project(":mirai-core-api").tasks["build"])
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai
|
||||
|
||||
import java.util.*
|
||||
import kotlin.reflect.full.companionObjectInstance
|
||||
|
||||
@JvmSynthetic
|
||||
internal actual fun findMiraiInstance(): IMirai {
|
||||
return ServiceLoader.load(IMirai::class.java).firstOrNull()
|
||||
?: Class.forName("net.mamoe.mirai.internal.MiraiImpl").kotlin.companionObjectInstance as IMirai
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2019-2021 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/master/LICENSE
|
||||
*/
|
||||
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.internal.utils.isSliderCaptchaSupportKind
|
||||
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() = 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]. Android 端没有默认验证码实现, [Default] 总为 `null`.
|
||||
*/
|
||||
@JvmField
|
||||
public actual val Default: LoginSolver? = null
|
||||
|
||||
@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")
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import android.util.Log
|
||||
|
||||
/**
|
||||
* [Log] 日志实现
|
||||
*
|
||||
* @see MiraiLogger.create
|
||||
* @see SingleFileLogger 使用单一文件记录日志
|
||||
* @see DirectoryLogger 在一个目录中按日期存放文件记录日志, 自动清理过期日志
|
||||
*/
|
||||
@MiraiInternalApi
|
||||
public actual open class PlatformLogger actual constructor(
|
||||
public override val identity: String?,
|
||||
) : MiraiLoggerPlatformBase() {
|
||||
|
||||
public override fun verbose0(message: String?) {
|
||||
Log.v(identity, message)
|
||||
}
|
||||
|
||||
public override fun verbose0(message: String?, e: Throwable?) {
|
||||
Log.v(identity, message, e)
|
||||
}
|
||||
|
||||
|
||||
public override fun info0(message: String?) {
|
||||
Log.i(identity, message)
|
||||
}
|
||||
|
||||
public override fun info0(message: String?, e: Throwable?) {
|
||||
Log.i(identity, message)
|
||||
}
|
||||
|
||||
|
||||
public override fun warning0(message: String?) {
|
||||
Log.w(identity, message)
|
||||
}
|
||||
|
||||
public override fun warning0(message: String?, e: Throwable?) {
|
||||
Log.w(identity, message)
|
||||
}
|
||||
|
||||
|
||||
public override fun error0(message: String?) {
|
||||
Log.e(identity, message)
|
||||
}
|
||||
|
||||
public override fun error0(message: String?, e: Throwable?) {
|
||||
Log.e(identity, message)
|
||||
}
|
||||
|
||||
|
||||
public override fun debug0(message: String?) {
|
||||
Log.d(identity, message)
|
||||
}
|
||||
|
||||
public override fun debug0(message: String?, e: Throwable?) {
|
||||
Log.d(identity, message)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2019-2021 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/master/LICENSE
|
||||
*/
|
||||
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import net.mamoe.mirai.internal.utils.StdoutLogger
|
||||
import java.io.File
|
||||
|
||||
|
||||
/**
|
||||
* 将日志写入('append')到特定文件.
|
||||
*
|
||||
* @see PlatformLogger 查看格式信息
|
||||
*/
|
||||
public actual class SingleFileLogger actual constructor(
|
||||
identity: String,
|
||||
file: File
|
||||
) : MiraiLogger by StdoutLogger(identity, { file.appendText(it + "\n") }) {
|
||||
public actual constructor(identity: String) : this(identity, File("$identity-${getCurrentDate()}.log"))
|
||||
|
||||
init {
|
||||
file.createNewFile()
|
||||
require(file.isFile) { "Log file must be a file: $file" }
|
||||
require(file.canWrite()) { "Log file must be write: $file" }
|
||||
}
|
||||
}
|
@ -131,9 +131,9 @@ internal fun Method.registerEventHandler(
|
||||
} else {
|
||||
// java methods
|
||||
|
||||
val paramType = this.parameters[0].type
|
||||
check(this.parameterCount == 1 && Event::class.java.isAssignableFrom(paramType)) {
|
||||
"Illegal method parameter. Required one exact Event subclass. found ${this.parameters.contentToString()}"
|
||||
val paramType = this.parameterTypes[0]
|
||||
check(this.parameterTypes.size == 1 && Event::class.java.isAssignableFrom(paramType)) {
|
||||
"Illegal method parameter. Required one exact Event subclass. found ${this.parameterTypes.contentToString()}"
|
||||
}
|
||||
suspend fun callMethod(event: Event): Any? {
|
||||
fun Method.invokeWithErrorReport(self: Any?, vararg args: Any?): Any? = try {
|
||||
|
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2020 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/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils
|
||||
|
||||
import net.mamoe.mirai.utils.*
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* JVM 控制台日志实现
|
||||
*
|
||||
*
|
||||
* 单条日志格式 (正则) 为:
|
||||
* ```regex
|
||||
* ^([\w-]*\s[\w:]*)\s(\w)\/(.*?):\s(.+)$
|
||||
* ```
|
||||
* 其中 group 分别为: 日期与时间, 严重程度, [identity], 消息内容.
|
||||
*
|
||||
* 示例:
|
||||
* ```log
|
||||
* 2020-05-21 19:51:09 V/Bot 1994701021: Send: OidbSvc.0x88d_7
|
||||
* ```
|
||||
*
|
||||
* 日期时间格式为 `yyyy-MM-dd HH:mm:ss`,
|
||||
*
|
||||
* 严重程度为 V, I, W, E. 分别对应 verbose, info, warning, error
|
||||
*
|
||||
* @param isColored 是否添加 ANSI 颜色
|
||||
*
|
||||
* @see MiraiLogger.create
|
||||
* @see SingleFileLogger 使用单一文件记录日志
|
||||
* @see DirectoryLogger 在一个目录中按日期存放文件记录日志, 自动清理过期日志
|
||||
*/
|
||||
internal open class StdoutLogger constructor(
|
||||
override val identity: String? = "Mirai",
|
||||
/**
|
||||
* 日志输出. 不会自动添加换行
|
||||
*/
|
||||
open val output: (String) -> Unit,
|
||||
val isColored: Boolean = true
|
||||
) : MiraiLoggerPlatformBase() {
|
||||
constructor(identity: String?) : this(identity, ::println)
|
||||
constructor(identity: String?, output: (String) -> Unit) : this(identity, output, true)
|
||||
|
||||
/**
|
||||
* 输出一条日志. [message] 末尾可能不带换行符.
|
||||
*/
|
||||
protected open fun printLog(message: String?, priority: SimpleLogger.LogPriority) {
|
||||
if (isColored) output("${priority.color}$currentTimeFormatted ${priority.simpleName}/$identity: $message${Color.RESET}")
|
||||
else output("$currentTimeFormatted ${priority.simpleName}/$identity: $message")
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 [SimpleLogger.LogPriority] 的颜色
|
||||
*/
|
||||
protected open val SimpleLogger.LogPriority.color: Color
|
||||
get() = when (this) {
|
||||
SimpleLogger.LogPriority.VERBOSE -> Color.RESET
|
||||
SimpleLogger.LogPriority.INFO -> Color.LIGHT_GREEN
|
||||
SimpleLogger.LogPriority.WARNING -> Color.LIGHT_RED
|
||||
SimpleLogger.LogPriority.ERROR -> Color.RED
|
||||
SimpleLogger.LogPriority.DEBUG -> Color.LIGHT_CYAN
|
||||
}
|
||||
|
||||
public override fun verbose0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.VERBOSE)
|
||||
|
||||
public override fun verbose0(message: String?, e: Throwable?) {
|
||||
if (e != null) verbose((message ?: e.toString()) + "\n${e.stackTraceToString()}")
|
||||
else verbose(message.toString())
|
||||
}
|
||||
|
||||
public override fun info0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.INFO)
|
||||
public override fun info0(message: String?, e: Throwable?) {
|
||||
if (e != null) info((message ?: e.toString()) + "\n${e.stackTraceToString()}")
|
||||
else info(message.toString())
|
||||
}
|
||||
|
||||
public override fun warning0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.WARNING)
|
||||
public override fun warning0(message: String?, e: Throwable?) {
|
||||
if (e != null) warning((message ?: e.toString()) + "\n${e.stackTraceToString()}")
|
||||
else warning(message.toString())
|
||||
}
|
||||
|
||||
public override fun error0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.ERROR)
|
||||
public override fun error0(message: String?, e: Throwable?) {
|
||||
if (e != null) error((message ?: e.toString()) + "\n${e.stackTraceToString()}")
|
||||
else error(message.toString())
|
||||
}
|
||||
|
||||
public override fun debug0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.DEBUG)
|
||||
public override fun debug0(message: String?, e: Throwable?) {
|
||||
if (e != null) debug((message ?: e.toString()) + "\n${e.stackTraceToString()}")
|
||||
else debug(message.toString())
|
||||
}
|
||||
protected open val timeFormat: DateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.SIMPLIFIED_CHINESE)
|
||||
|
||||
private val currentTimeFormatted get() = timeFormat.format(Date())
|
||||
|
||||
@MiraiExperimentalApi("This is subject to change.")
|
||||
protected enum class Color(private val format: String) {
|
||||
RESET("\u001b[0m"),
|
||||
|
||||
WHITE("\u001b[30m"),
|
||||
RED("\u001b[31m"),
|
||||
EMERALD_GREEN("\u001b[32m"),
|
||||
GOLD("\u001b[33m"),
|
||||
BLUE("\u001b[34m"),
|
||||
PURPLE("\u001b[35m"),
|
||||
GREEN("\u001b[36m"),
|
||||
|
||||
GRAY("\u001b[90m"),
|
||||
LIGHT_RED("\u001b[91m"),
|
||||
LIGHT_GREEN("\u001b[92m"),
|
||||
LIGHT_YELLOW("\u001b[93m"),
|
||||
LIGHT_BLUE("\u001b[94m"),
|
||||
LIGHT_PURPLE("\u001b[95m"),
|
||||
LIGHT_CYAN("\u001b[96m")
|
||||
;
|
||||
|
||||
override fun toString(): String = format
|
||||
}
|
||||
}
|
@ -91,7 +91,8 @@ private fun String.forEachMiraiCode(block: (origin: String, name: String?, args:
|
||||
}
|
||||
}
|
||||
|
||||
private object MiraiCodeParsers : Map<String, MiraiCodeParser> by mapOf(
|
||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||
private object MiraiCodeParsers: AbstractMap<String, MiraiCodeParser>(), Map<String, MiraiCodeParser> by mapOf(
|
||||
"at" to MiraiCodeParser(Regex("""(\d*)""")) { (target) ->
|
||||
At(target.toLong())
|
||||
},
|
||||
|
@ -119,7 +119,7 @@ public sealed class CustomMessage : SingleMessage {
|
||||
private val factories: ConcurrentLinkedQueue<Factory<*>> = ConcurrentLinkedQueue()
|
||||
|
||||
internal fun register(factory: Factory<out CustomMessage>) {
|
||||
factories.removeIf { it::class == factory::class }
|
||||
factories.removeAll { it::class == factory::class }
|
||||
val exist = factories.firstOrNull { it.typeName == factory.typeName }
|
||||
if (exist != null) {
|
||||
error("CustomMessage.Factory typeName ${factory.typeName} is already registered by ${exist::class.qualifiedName}")
|
||||
|
@ -18,6 +18,7 @@ import net.mamoe.mirai.message.data.Image.Key.IMAGE_ID_REGEX
|
||||
import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_1
|
||||
import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_2
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalApi
|
||||
import net.mamoe.mirai.utils.replaceAllKotlin
|
||||
import kotlin.native.concurrent.SharedImmutable
|
||||
|
||||
// region image
|
||||
@ -128,7 +129,7 @@ internal fun constrainSingleMessagesImpl(sequence: Sequence<SingleMessage>): Lis
|
||||
if (singleMessage is ConstrainSingle) {
|
||||
val key = singleMessage.key.topmostKey
|
||||
val firstOccurrence = list.first { it != null && key.isInstance(it) } // may be singleMessage itself
|
||||
list.replaceAll {
|
||||
list.replaceAllKotlin {
|
||||
when {
|
||||
it == null -> null
|
||||
it === firstOccurrence -> singleMessage
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2021 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.
|
||||
@ -13,26 +13,8 @@ import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
private val currentDay get() = Calendar.getInstance()[Calendar.DAY_OF_MONTH]
|
||||
private val currentDate get() = SimpleDateFormat("yyyy-MM-dd").format(Date())
|
||||
|
||||
/**
|
||||
* 将日志写入('append')到特定文件.
|
||||
*
|
||||
* @see PlatformLogger 查看格式信息
|
||||
*/
|
||||
public class SingleFileLogger @JvmOverloads constructor(
|
||||
identity: String,
|
||||
file: File = File("$identity-$currentDate.log")
|
||||
) :
|
||||
PlatformLogger(identity, { file.appendText(it + "\n") }) {
|
||||
|
||||
init {
|
||||
file.createNewFile()
|
||||
require(file.isFile) { "Log file must be a file: $file" }
|
||||
require(file.canWrite()) { "Log file must be write: $file" }
|
||||
}
|
||||
}
|
||||
internal fun getCurrentDay() = Calendar.getInstance()[Calendar.DAY_OF_MONTH]
|
||||
internal fun getCurrentDate() = SimpleDateFormat("yyyy-MM-dd").format(Date())
|
||||
|
||||
private val STUB: (priority: SimpleLogger.LogPriority, message: String?, e: Throwable?) -> Unit =
|
||||
{ _: SimpleLogger.LogPriority, _: String?, _: Throwable? -> error("stub") }
|
||||
@ -61,15 +43,15 @@ public class DirectoryLogger @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private var day = currentDay
|
||||
private var day = getCurrentDay()
|
||||
|
||||
private var delegate: SingleFileLogger = SingleFileLogger(identity, File(directory, "$currentDate.log"))
|
||||
private var delegate: SingleFileLogger = SingleFileLogger(identity, File(directory, "${getCurrentDate()}.log"))
|
||||
get() {
|
||||
val currentDay = currentDay
|
||||
val currentDay = getCurrentDay()
|
||||
if (day != currentDay) {
|
||||
day = currentDay
|
||||
checkOutdated()
|
||||
field = SingleFileLogger(identity!!, File(directory, "$currentDate.log"))
|
||||
field = SingleFileLogger(identity!!, File(directory, "${getCurrentDate()}.log"))
|
||||
}
|
||||
return field
|
||||
}
|
||||
|
@ -9,28 +9,11 @@
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.io.*
|
||||
import kotlinx.coroutines.io.jvm.nio.copyTo
|
||||
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.DeviceInfo.Companion.loadAsDeviceInfo
|
||||
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
|
||||
import java.io.File
|
||||
import java.io.RandomAccessFile
|
||||
import javax.imageio.ImageIO
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/**
|
||||
* 验证码, 设备锁解决器
|
||||
@ -38,7 +21,7 @@ import kotlin.coroutines.CoroutineContext
|
||||
* @see Default
|
||||
* @see BotConfiguration.loginSolver
|
||||
*/
|
||||
public abstract class LoginSolver {
|
||||
public expect abstract class LoginSolver() {
|
||||
/**
|
||||
* 处理图片验证码.
|
||||
*
|
||||
@ -54,7 +37,6 @@ public abstract class LoginSolver {
|
||||
* 否则会跳过滑动验证码并告诉服务器此客户端不支持, 有可能导致登录失败
|
||||
*/
|
||||
public open val isSliderCaptchaSupported: Boolean
|
||||
get() = isSliderCaptchaSupportKind ?: true
|
||||
|
||||
/**
|
||||
* 处理滑动验证码.
|
||||
@ -83,215 +65,25 @@ public abstract class LoginSolver {
|
||||
* 当前平台默认的 [LoginSolver]。
|
||||
*
|
||||
* 检测策略:
|
||||
* 1. 检测 `android.util.Log`, 如果存在, 返回 `null`.
|
||||
* 2. 检测 JVM 属性 `mirai.no-desktop`. 若存在, 返回 [StandardCharImageLoginSolver]
|
||||
* 3. 检测 JVM 桌面环境, 若支持, 返回 [SwingSolver]
|
||||
* 4. 返回 [StandardCharImageLoginSolver]
|
||||
* 1. 若是 `mirai-core-api-android` 或 `android.util.Log` 存在, 返回 `null`.
|
||||
* 2. 检测 JVM 属性 `mirai.no-desktop`. 若存在, 返回 `StandardCharImageLoginSolver`
|
||||
* 3. 检测 JVM 桌面环境, 若支持, 返回 `SwingSolver`
|
||||
* 4. 返回 `StandardCharImageLoginSolver`
|
||||
*
|
||||
* @return [SwingSolver] 或 [StandardCharImageLoginSolver] 或 `null`
|
||||
* @return `SwingSolver` 或 `StandardCharImageLoginSolver` 或 `null`
|
||||
*/
|
||||
@JvmField
|
||||
public val Default: LoginSolver? = when (WindowHelperJvm.platformKind) {
|
||||
WindowHelperJvm.PlatformKind.ANDROID -> null
|
||||
WindowHelperJvm.PlatformKind.SWING -> {
|
||||
when (isSliderCaptchaSupportKind) {
|
||||
null, false -> SwingSolver
|
||||
true -> SeleniumLoginSolver ?: SwingSolver
|
||||
}
|
||||
}
|
||||
WindowHelperJvm.PlatformKind.CLI -> StandardCharImageLoginSolver()
|
||||
}
|
||||
public val Default: LoginSolver?
|
||||
|
||||
@Suppress("unused")
|
||||
@Deprecated("Binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
public fun getDefault(): LoginSolver = Default
|
||||
?: error("LoginSolver is not provided by default on your platform. Please specify by BotConfiguration.loginSolver")
|
||||
public fun getDefault(): LoginSolver
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* CLI 环境 [LoginSolver]. 将验证码图片转为字符画并通过 `output` 输出, [input] 获取用户输入.
|
||||
*
|
||||
* 使用字符图片展示验证码, 使用 [input] 获取输入, 使用 [loggerSupplier] 输出
|
||||
*
|
||||
* @see createBlocking
|
||||
*/
|
||||
public class StandardCharImageLoginSolver @JvmOverloads constructor(
|
||||
input: suspend () -> String = { readLine() ?: throw NoStandardInputForCaptchaException() },
|
||||
/**
|
||||
* 为 `null` 时使用 [Bot.logger]
|
||||
*/
|
||||
private val loggerSupplier: (bot: Bot) -> MiraiLogger = { it.logger }
|
||||
) : LoginSolver() {
|
||||
public constructor(
|
||||
input: suspend () -> String = { readLine() ?: throw NoStandardInputForCaptchaException() },
|
||||
overrideLogger: MiraiLogger?
|
||||
) : this(input, { overrideLogger ?: it.logger })
|
||||
|
||||
private val input: suspend () -> String = suspend {
|
||||
withContext(Dispatchers.IO) { input() }
|
||||
}
|
||||
|
||||
override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? = loginSolverLock.withLock {
|
||||
val logger = loggerSupplier(bot)
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
withContext(Dispatchers.IO) {
|
||||
val tempFile: File = File.createTempFile("tmp", ".png").apply { deleteOnExit() }
|
||||
tempFile.createNewFile()
|
||||
logger.info { "[PicCaptcha] 需要图片验证码登录, 验证码为 4 字母" }
|
||||
logger.info { "[PicCaptcha] Picture captcha required. Captcha consists of 4 letters." }
|
||||
try {
|
||||
tempFile.writeChannel().apply { writeFully(data); close() }
|
||||
logger.info { "[PicCaptcha] 将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}" }
|
||||
logger.info { "[PicCaptcha] Displaying char-image. If not clear, view file ${tempFile.absolutePath}" }
|
||||
} catch (e: Exception) {
|
||||
logger.warning("[PicCaptcha] 无法写出验证码文件, 请尝试查看以上字符图片", e)
|
||||
logger.warning("[PicCaptcha] Failed to export captcha image. Please see the char-image.", e)
|
||||
}
|
||||
|
||||
tempFile.inputStream().use { stream ->
|
||||
try {
|
||||
val img = ImageIO.read(stream)
|
||||
if (img == null) {
|
||||
logger.warning { "[PicCaptcha] 无法创建字符图片. 请查看文件" }
|
||||
logger.warning { "[PicCaptcha] Failed to create char-image. Please see the file." }
|
||||
} else {
|
||||
logger.info { "[PicCaptcha] \n" + img.createCharImg() }
|
||||
}
|
||||
} catch (throwable: Throwable) {
|
||||
logger.warning("[PicCaptcha] 创建字符图片时出错. 请查看文件.", throwable)
|
||||
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 {
|
||||
logger.info { "[PicCaptcha] 正在提交 $it..." }
|
||||
logger.info { "[PicCaptcha] Submitting $it..." }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String = loginSolverLock.withLock {
|
||||
val logger = loggerSupplier(bot)
|
||||
logger.info { "[SliderCaptcha] 需要滑动验证码, 请在浏览器中打开以下链接并完成验证码, 完成后请输入提示 ticket." }
|
||||
logger.info { "[SliderCaptcha] Slider captcha required, please open the following link in any browser and solve the captcha. Type ticket here after completion." }
|
||||
logger.info { "[SliderCaptcha] @see https://github.com/project-mirai/mirai-login-solver-selenium#%E4%B8%8B%E8%BD%BD-chrome-%E6%89%A9%E5%B1%95%E6%8F%92%E4%BB%B6" }
|
||||
logger.info(url)
|
||||
return input().also {
|
||||
logger.info { "[SliderCaptcha] 正在提交中..." }
|
||||
logger.info { "[SliderCaptcha] Submitting..." }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String = loginSolverLock.withLock {
|
||||
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..." }
|
||||
}
|
||||
}
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* 创建 Java 阻塞版 [input] 的 [StandardCharImageLoginSolver]
|
||||
*
|
||||
* @param input 将在协程 IO 池执行, 可以有阻塞调用
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun createBlocking(input: () -> String, output: MiraiLogger?): StandardCharImageLoginSolver {
|
||||
return StandardCharImageLoginSolver({ withContext(Dispatchers.IO) { input() } }, output)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Java 阻塞版 [input] 的 [StandardCharImageLoginSolver]
|
||||
*
|
||||
* @param input 将在协程 IO 池执行, 可以有阻塞调用
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun createBlocking(input: () -> String): StandardCharImageLoginSolver {
|
||||
return StandardCharImageLoginSolver({ withContext(Dispatchers.IO) { input() } })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
//////////////// internal
|
||||
///////////////////////////////
|
||||
|
||||
internal fun BotConfiguration.getFileBasedDeviceInfoSupplier(file: () -> File): (Bot) -> DeviceInfo {
|
||||
return {
|
||||
file().loadAsDeviceInfo(json)
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from Ktor CIO
|
||||
private fun File.writeChannel(
|
||||
coroutineContext: CoroutineContext = Dispatchers.IO
|
||||
): ByteWriteChannel = GlobalScope.reader(CoroutineName("file-writer") + coroutineContext, autoFlush = true) {
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
RandomAccessFile(this@writeChannel, "rw").use { file ->
|
||||
val copied = channel.copyTo(file.channel)
|
||||
file.setLength(copied) // truncate tail that could remain from the previously written data
|
||||
}
|
||||
}.channel
|
||||
|
||||
private val loginSolverLock = Mutex()
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
private fun BufferedImage.createCharImg(outputWidth: Int = 100, ignoreRate: Double = 0.95): String {
|
||||
val newHeight = (this.height * (outputWidth.toDouble() / this.width)).toInt()
|
||||
val tmp = this.getScaledInstance(outputWidth, newHeight, Image.SCALE_SMOOTH)
|
||||
val image = BufferedImage(outputWidth, newHeight, BufferedImage.TYPE_INT_ARGB)
|
||||
val g2d = image.createGraphics()
|
||||
g2d.drawImage(tmp, 0, 0, null)
|
||||
fun gray(rgb: Int): Int {
|
||||
val r = rgb and 0xff0000 shr 16
|
||||
val g = rgb and 0x00ff00 shr 8
|
||||
val b = rgb and 0x0000ff
|
||||
return (r * 30 + g * 59 + b * 11 + 50) / 100
|
||||
}
|
||||
|
||||
fun grayCompare(g1: Int, g2: Int): Boolean =
|
||||
kotlin.math.min(g1, g2).toDouble() / kotlin.math.max(g1, g2) >= ignoreRate
|
||||
|
||||
val background = gray(image.getRGB(0, 0))
|
||||
|
||||
return buildString(capacity = height) {
|
||||
|
||||
val lines = mutableListOf<StringBuilder>()
|
||||
|
||||
var minXPos = outputWidth
|
||||
var maxXPos = 0
|
||||
|
||||
for (y in 0 until image.height) {
|
||||
val builderLine = StringBuilder()
|
||||
for (x in 0 until image.width) {
|
||||
val gray = gray(image.getRGB(x, y))
|
||||
if (grayCompare(gray, background)) {
|
||||
builderLine.append(" ")
|
||||
} else {
|
||||
builderLine.append("#")
|
||||
if (x < minXPos) {
|
||||
minXPos = x
|
||||
}
|
||||
if (x > maxXPos) {
|
||||
maxXPos = x
|
||||
}
|
||||
}
|
||||
}
|
||||
if (builderLine.toString().isBlank()) {
|
||||
continue
|
||||
}
|
||||
lines.add(builderLine)
|
||||
}
|
||||
for (line in lines) {
|
||||
append(line.substring(minXPos, maxXPos)).append("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -222,8 +222,8 @@ public inline fun MiraiLogger.error(message: () -> String?, e: Throwable?) {
|
||||
|
||||
/**
|
||||
* 当前平台的默认的日志记录器.
|
||||
* 在 _JVM 控制台_ 端的实现为 [println]
|
||||
* 在 _Android_ 端的实现为 `android.util.Log`
|
||||
* - 在 _JVM 控制台_ 端的实现为 [println]
|
||||
* - 在 _Android_ 端的实现为 `android.util.Log`
|
||||
*
|
||||
*
|
||||
* 单条日志格式 (正则) 为:
|
||||
@ -244,14 +244,9 @@ public inline fun MiraiLogger.error(message: () -> String?, e: Throwable?) {
|
||||
* @see MiraiLogger.create
|
||||
*/
|
||||
@MiraiInternalApi
|
||||
public expect open class PlatformLogger constructor(
|
||||
public expect open class PlatformLogger @JvmOverloads constructor(
|
||||
identity: String? = "Mirai",
|
||||
output: (String) -> Unit, // TODO: 2020/11/30 review logs, currently it's just for compile
|
||||
) : MiraiLoggerPlatformBase {
|
||||
@JvmOverloads
|
||||
public constructor(identity: String? = "Mirai")
|
||||
}
|
||||
|
||||
) : MiraiLoggerPlatformBase
|
||||
|
||||
/**
|
||||
* 不做任何事情的 logger, keep silent.
|
||||
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2019-2021 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/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:JvmName("FileLoggerKt") // bin-comp
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* 将日志写入('append')到特定文件.
|
||||
*
|
||||
* @see PlatformLogger 查看格式信息
|
||||
*/
|
||||
public expect class SingleFileLogger : MiraiLogger {
|
||||
public constructor(identity: String)
|
||||
public constructor(identity: String, file: File = File("$identity-${getCurrentDate()}.log"))
|
||||
|
||||
// Implementation notes v2.5.0:
|
||||
// default argument `file` to produce synthetic constructor with `DefaultConstructorMarker` for binary compatibility
|
||||
// dedicated constructor with single parameter `identity` for the same reason.
|
||||
}
|
292
mirai-core-api/src/jvmMain/kotlin/utils/LoginSolver.jvm.kt
Normal file
292
mirai-core-api/src/jvmMain/kotlin/utils/LoginSolver.jvm.kt
Normal file
@ -0,0 +1,292 @@
|
||||
/*
|
||||
* Copyright 2019-2021 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/master/LICENSE
|
||||
*/
|
||||
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.io.ByteWriteChannel
|
||||
import kotlinx.coroutines.io.close
|
||||
import kotlinx.coroutines.io.jvm.nio.copyTo
|
||||
import kotlinx.coroutines.io.reader
|
||||
import kotlinx.coroutines.io.writeFully
|
||||
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
|
||||
import java.io.File
|
||||
import java.io.RandomAccessFile
|
||||
import javax.imageio.ImageIO
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
|
||||
/**
|
||||
* 验证码, 设备锁解决器
|
||||
*
|
||||
* @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) {
|
||||
WindowHelperJvm.PlatformKind.ANDROID -> null
|
||||
WindowHelperJvm.PlatformKind.SWING -> {
|
||||
when (isSliderCaptchaSupportKind) {
|
||||
null, false -> SwingSolver
|
||||
true -> SeleniumLoginSolver ?: SwingSolver
|
||||
}
|
||||
}
|
||||
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] 获取用户输入.
|
||||
*
|
||||
* 使用字符图片展示验证码, 使用 [input] 获取输入, 使用 [loggerSupplier] 输出
|
||||
*
|
||||
* @see createBlocking
|
||||
*/
|
||||
public class StandardCharImageLoginSolver @JvmOverloads constructor(
|
||||
input: suspend () -> String = { readLine() ?: throw NoStandardInputForCaptchaException() },
|
||||
/**
|
||||
* 为 `null` 时使用 [Bot.logger]
|
||||
*/
|
||||
private val loggerSupplier: (bot: Bot) -> MiraiLogger = { it.logger }
|
||||
) : LoginSolver() {
|
||||
public constructor(
|
||||
input: suspend () -> String = { readLine() ?: throw NoStandardInputForCaptchaException() },
|
||||
overrideLogger: MiraiLogger?
|
||||
) : this(input, { overrideLogger ?: it.logger })
|
||||
|
||||
private val input: suspend () -> String = suspend {
|
||||
withContext(Dispatchers.IO) { input() }
|
||||
}
|
||||
|
||||
override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? = loginSolverLock.withLock {
|
||||
val logger = loggerSupplier(bot)
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
(withContext(Dispatchers.IO) {
|
||||
val tempFile: File = File.createTempFile("tmp", ".png").apply { deleteOnExit() }
|
||||
tempFile.createNewFile()
|
||||
logger.info { "[PicCaptcha] 需要图片验证码登录, 验证码为 4 字母" }
|
||||
logger.info { "[PicCaptcha] Picture captcha required. Captcha consists of 4 letters." }
|
||||
try {
|
||||
tempFile.writeChannel().apply { writeFully(data); close() }
|
||||
logger.info { "[PicCaptcha] 将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}" }
|
||||
logger.info { "[PicCaptcha] Displaying char-image. If not clear, view file ${tempFile.absolutePath}" }
|
||||
} catch (e: Exception) {
|
||||
logger.warning("[PicCaptcha] 无法写出验证码文件, 请尝试查看以上字符图片", e)
|
||||
logger.warning("[PicCaptcha] Failed to export captcha image. Please see the char-image.", e)
|
||||
}
|
||||
|
||||
tempFile.inputStream().use { stream ->
|
||||
try {
|
||||
val img = ImageIO.read(stream)
|
||||
if (img == null) {
|
||||
logger.warning { "[PicCaptcha] 无法创建字符图片. 请查看文件" }
|
||||
logger.warning { "[PicCaptcha] Failed to create char-image. Please see the file." }
|
||||
} else {
|
||||
logger.info { "[PicCaptcha] \n" + img.createCharImg() }
|
||||
}
|
||||
} catch (throwable: Throwable) {
|
||||
logger.warning("[PicCaptcha] 创建字符图片时出错. 请查看文件.", throwable)
|
||||
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 {
|
||||
logger.info { "[PicCaptcha] 正在提交 $it..." }
|
||||
logger.info { "[PicCaptcha] Submitting $it..." }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String = loginSolverLock.withLock {
|
||||
val logger = loggerSupplier(bot)
|
||||
logger.info { "[SliderCaptcha] 需要滑动验证码, 请在浏览器中打开以下链接并完成验证码, 完成后请输入提示 ticket." }
|
||||
logger.info { "[SliderCaptcha] Slider captcha required, please open the following link in any browser and solve the captcha. Type ticket here after completion." }
|
||||
logger.info { "[SliderCaptcha] @see https://github.com/project-mirai/mirai-login-solver-selenium#%E4%B8%8B%E8%BD%BD-chrome-%E6%89%A9%E5%B1%95%E6%8F%92%E4%BB%B6" }
|
||||
logger.info(url)
|
||||
return input().also {
|
||||
logger.info { "[SliderCaptcha] 正在提交中..." }
|
||||
logger.info { "[SliderCaptcha] Submitting..." }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String = loginSolverLock.withLock {
|
||||
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..." }
|
||||
}
|
||||
}
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* 创建 Java 阻塞版 [input] 的 [StandardCharImageLoginSolver]
|
||||
*
|
||||
* @param input 将在协程 IO 池执行, 可以有阻塞调用
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun createBlocking(input: () -> String, output: MiraiLogger?): StandardCharImageLoginSolver {
|
||||
return StandardCharImageLoginSolver({ withContext(Dispatchers.IO) { input() } }, output)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Java 阻塞版 [input] 的 [StandardCharImageLoginSolver]
|
||||
*
|
||||
* @param input 将在协程 IO 池执行, 可以有阻塞调用
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun createBlocking(input: () -> String): StandardCharImageLoginSolver {
|
||||
return StandardCharImageLoginSolver({ withContext(Dispatchers.IO) { input() } })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from Ktor CIO
|
||||
private fun File.writeChannel(
|
||||
coroutineContext: CoroutineContext = Dispatchers.IO
|
||||
): ByteWriteChannel = GlobalScope.reader(CoroutineName("file-writer") + coroutineContext, autoFlush = true) {
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
RandomAccessFile(this@writeChannel, "rw").use { file ->
|
||||
val copied = channel.copyTo(file.channel)
|
||||
file.setLength(copied) // truncate tail that could remain from the previously written data
|
||||
}
|
||||
}.channel
|
||||
|
||||
private val loginSolverLock = Mutex()
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
private fun BufferedImage.createCharImg(outputWidth: Int = 100, ignoreRate: Double = 0.95): String {
|
||||
val newHeight = (this.height * (outputWidth.toDouble() / this.width)).toInt()
|
||||
val tmp = this.getScaledInstance(outputWidth, newHeight, Image.SCALE_SMOOTH)
|
||||
val image = BufferedImage(outputWidth, newHeight, BufferedImage.TYPE_INT_ARGB)
|
||||
val g2d = image.createGraphics()
|
||||
g2d.drawImage(tmp, 0, 0, null)
|
||||
fun gray(rgb: Int): Int {
|
||||
val r = rgb and 0xff0000 shr 16
|
||||
val g = rgb and 0x00ff00 shr 8
|
||||
val b = rgb and 0x0000ff
|
||||
return (r * 30 + g * 59 + b * 11 + 50) / 100
|
||||
}
|
||||
|
||||
fun grayCompare(g1: Int, g2: Int): Boolean =
|
||||
kotlin.math.min(g1, g2).toDouble() / kotlin.math.max(g1, g2) >= ignoreRate
|
||||
|
||||
val background = gray(image.getRGB(0, 0))
|
||||
|
||||
return buildString(capacity = height) {
|
||||
|
||||
val lines = mutableListOf<StringBuilder>()
|
||||
|
||||
var minXPos = outputWidth
|
||||
var maxXPos = 0
|
||||
|
||||
for (y in 0 until image.height) {
|
||||
val builderLine = StringBuilder()
|
||||
for (x in 0 until image.width) {
|
||||
val gray = gray(image.getRGB(x, y))
|
||||
if (grayCompare(gray, background)) {
|
||||
builderLine.append(" ")
|
||||
} else {
|
||||
builderLine.append("#")
|
||||
if (x < minXPos) {
|
||||
minXPos = x
|
||||
}
|
||||
if (x > maxXPos) {
|
||||
maxXPos = x
|
||||
}
|
||||
}
|
||||
}
|
||||
if (builderLine.toString().isBlank()) {
|
||||
continue
|
||||
}
|
||||
lines.add(builderLine)
|
||||
}
|
||||
for (line in lines) {
|
||||
append(line.substring(minXPos, maxXPos)).append("\n")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2021 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.
|
||||
@ -41,7 +41,7 @@ import java.util.*
|
||||
* @see DirectoryLogger 在一个目录中按日期存放文件记录日志, 自动清理过期日志
|
||||
*/
|
||||
@MiraiInternalApi
|
||||
public actual open class PlatformLogger constructor(
|
||||
public actual open class PlatformLogger constructor( // same as StdoutLogger but doesn't matter
|
||||
public override val identity: String? = "Mirai",
|
||||
/**
|
||||
* 日志输出. 不会自动添加换行
|
||||
@ -49,8 +49,11 @@ public actual open class PlatformLogger constructor(
|
||||
public open val output: (String) -> Unit,
|
||||
public val isColored: Boolean = true
|
||||
) : MiraiLoggerPlatformBase() {
|
||||
|
||||
// PlatformLogger("") resolves to this one.
|
||||
public actual constructor(identity: String?) : this(identity, ::println)
|
||||
public actual constructor(identity: String?, output: (String) -> Unit) : this(identity, output, true)
|
||||
|
||||
public constructor(identity: String?, output: (String) -> Unit = ::println) : this(identity, output, true)
|
||||
|
||||
/**
|
||||
* 输出一条日志. [message] 末尾可能不带换行符.
|
||||
@ -75,33 +78,34 @@ public actual open class PlatformLogger constructor(
|
||||
public override fun verbose0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.VERBOSE)
|
||||
|
||||
public override fun verbose0(message: String?, e: Throwable?) {
|
||||
if (e != null) verbose((message ?: e.toString()) + "\n${e.stackTraceString}")
|
||||
if (e != null) verbose((message ?: e.toString()) + "\n${e.stackTraceToString()}")
|
||||
else verbose(message.toString())
|
||||
}
|
||||
|
||||
public override fun info0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.INFO)
|
||||
public override fun info0(message: String?, e: Throwable?) {
|
||||
if (e != null) info((message ?: e.toString()) + "\n${e.stackTraceString}")
|
||||
if (e != null) info((message ?: e.toString()) + "\n${e.stackTraceToString()}")
|
||||
else info(message.toString())
|
||||
}
|
||||
|
||||
public override fun warning0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.WARNING)
|
||||
public override fun warning0(message: String?, e: Throwable?) {
|
||||
if (e != null) warning((message ?: e.toString()) + "\n${e.stackTraceString}")
|
||||
if (e != null) warning((message ?: e.toString()) + "\n${e.stackTraceToString()}")
|
||||
else warning(message.toString())
|
||||
}
|
||||
|
||||
public override fun error0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.ERROR)
|
||||
public override fun error0(message: String?, e: Throwable?) {
|
||||
if (e != null) error((message ?: e.toString()) + "\n${e.stackTraceString}")
|
||||
if (e != null) error((message ?: e.toString()) + "\n${e.stackTraceToString()}")
|
||||
else error(message.toString())
|
||||
}
|
||||
|
||||
public override fun debug0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.DEBUG)
|
||||
public override fun debug0(message: String?, e: Throwable?) {
|
||||
if (e != null) debug((message ?: e.toString()) + "\n${e.stackTraceString}")
|
||||
if (e != null) debug((message ?: e.toString()) + "\n${e.stackTraceToString()}")
|
||||
else debug(message.toString())
|
||||
}
|
||||
|
||||
protected open val timeFormat: DateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.SIMPLIFIED_CHINESE)
|
||||
|
||||
private val currentTimeFormatted get() = timeFormat.format(Date())
|
||||
@ -129,8 +133,4 @@ public actual open class PlatformLogger constructor(
|
||||
|
||||
override fun toString(): String = format
|
||||
}
|
||||
}
|
||||
|
||||
@get:JvmSynthetic
|
||||
internal val Throwable.stackTraceString
|
||||
get() = this.stackTraceToString()
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2019-2021 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/master/LICENSE
|
||||
*/
|
||||
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import java.io.File
|
||||
|
||||
|
||||
/**
|
||||
* 将日志写入('append')到特定文件.
|
||||
*
|
||||
* @see PlatformLogger 查看格式信息
|
||||
*/
|
||||
public actual class SingleFileLogger actual constructor(
|
||||
identity: String,
|
||||
file: File
|
||||
) : MiraiLogger, PlatformLogger(identity, { file.appendText(it + "\n") }) {
|
||||
// Implementation notes v2.5.0:
|
||||
// Extending `PlatformLogger` for binary compatibility for JVM target only.
|
||||
// See actual declaration in androidMain for a better impl (implements `MiraiLogger` only)
|
||||
|
||||
public actual constructor(identity: String) : this(identity, File("$identity-${getCurrentDate()}.log"))
|
||||
|
||||
init {
|
||||
file.createNewFile()
|
||||
require(file.isFile) { "Log file must be a file: $file" }
|
||||
require(file.canWrite()) { "Log file must be write: $file" }
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2021 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.
|
@ -15,7 +15,7 @@ plugins {
|
||||
kotlin("multiplatform")
|
||||
kotlin("plugin.serialization")
|
||||
|
||||
id("kotlinx-atomicfu")
|
||||
//id("kotlinx-atomicfu")
|
||||
id("net.mamoe.kotlin-jvm-blocking-bridge")
|
||||
`maven-publish`
|
||||
id("com.jfrog.bintray")
|
||||
@ -23,29 +23,20 @@ plugins {
|
||||
|
||||
description = "mirai-core utilities"
|
||||
|
||||
val isAndroidSDKAvailable: Boolean by project
|
||||
|
||||
kotlin {
|
||||
explicitApi()
|
||||
|
||||
if (isAndroidSDKAvailable) {
|
||||
apply(from = rootProject.file("gradle/android.gradle"))
|
||||
android("android") {
|
||||
publishAllLibraryVariants()
|
||||
// apply(from = rootProject.file("gradle/android.gradle"))
|
||||
// android("android") {
|
||||
// publishAllLibraryVariants()
|
||||
// }
|
||||
jvm("android") {
|
||||
attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.androidJvm)
|
||||
// publishAllLibraryVariants()
|
||||
}
|
||||
} else {
|
||||
println(
|
||||
"""Android SDK 可能未安装.
|
||||
$name 的 Android 目标编译将不会进行.
|
||||
这不会影响 Android 以外的平台的编译.
|
||||
""".trimIndent()
|
||||
)
|
||||
println(
|
||||
"""Android SDK might not be installed.
|
||||
Android target of $name will not be compiled.
|
||||
It does no influence on the compilation of other platforms.
|
||||
""".trimIndent()
|
||||
)
|
||||
printAndroidNotInstalled()
|
||||
}
|
||||
|
||||
jvm("common") {
|
||||
@ -79,8 +70,10 @@ kotlin {
|
||||
}
|
||||
|
||||
if (isAndroidSDKAvailable) {
|
||||
androidMain {
|
||||
val androidMain by getting {
|
||||
//
|
||||
dependencies {
|
||||
compileOnly(`android-runtime`)
|
||||
api1(`ktor-client-android`)
|
||||
}
|
||||
}
|
||||
@ -96,6 +89,19 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("checkAndroidApiLevel") {
|
||||
doFirst {
|
||||
androidutil.AndroidApiLevelCheck.check(
|
||||
buildDir.resolve("classes/kotlin/android/main"),
|
||||
project.property("mirai.android.target.api.level")!!.toString().toInt(),
|
||||
project
|
||||
)
|
||||
}
|
||||
group = "verification"
|
||||
this.mustRunAfter("androidMainClasses")
|
||||
}
|
||||
tasks.getByName("androidTest").dependsOn("checkAndroidApiLevel")
|
||||
|
||||
fun org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler.implementation1(dependencyNotation: String) =
|
||||
implementation(dependencyNotation) {
|
||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib")
|
||||
|
20
mirai-core-utils/src/androidMain/kotlin/Actuals.kt
Normal file
20
mirai-core-utils/src/androidMain/kotlin/Actuals.kt
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2019-2021 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/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:JvmMultifileClass
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import android.util.Base64
|
||||
|
||||
|
||||
public actual fun ByteArray.encodeToBase64(): String {
|
||||
return Base64.encodeToString(this, Base64.DEFAULT)
|
||||
}
|
@ -152,8 +152,7 @@ public fun UByteArray.toUHexString(separator: String = " ", offset: Int = 0, len
|
||||
public inline fun ByteArray.encodeToString(offset: Int = 0, charset: Charset = Charsets.UTF_8): String =
|
||||
kotlinx.io.core.String(this, charset = charset, offset = offset, length = this.size - offset)
|
||||
|
||||
public inline fun ByteArray.encodeToBase64(): String =
|
||||
Base64.getEncoder().encodeToString(this)
|
||||
public expect fun ByteArray.encodeToBase64(): String
|
||||
|
||||
public inline fun ByteArray.toReadPacket(offset: Int = 0, length: Int = this.size - offset): ByteReadPacket =
|
||||
ByteReadPacket(this, offset = offset, length = length)
|
||||
|
@ -131,7 +131,7 @@ public fun Input.copyTo(out: OutputStream, bufferSize: Int = DEFAULT_BUFFER_SIZE
|
||||
return bytesCopied
|
||||
}
|
||||
|
||||
public inline fun <I : AutoCloseable, O : AutoCloseable, R> I.withOut(output: O, block: I.(output: O) -> R): R {
|
||||
public inline fun <I : Closeable, O : Closeable, R> I.withOut(output: O, block: I.(output: O) -> R): R {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
|
@ -157,3 +157,10 @@ public inline fun <R> runCatchingExceptions(block: () -> R): Result<R> {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
public inline fun <E> MutableList<E>.replaceAllKotlin(operator: (E) -> E) {
|
||||
val li: MutableListIterator<E> = this.listIterator()
|
||||
while (li.hasNext()) {
|
||||
li.set(operator(li.next()))
|
||||
}
|
||||
}
|
20
mirai-core-utils/src/jvmMain/kotlin/Actuals.kt
Normal file
20
mirai-core-utils/src/jvmMain/kotlin/Actuals.kt
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2019-2021 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/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:JvmMultifileClass
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import java.util.*
|
||||
|
||||
|
||||
public actual fun ByteArray.encodeToBase64(): String {
|
||||
return Base64.getEncoder().encodeToString(this)
|
||||
}
|
14
mirai-core-utils/src/main/AndroidManifest.xml
Normal file
14
mirai-core-utils/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2019-2020 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/master/LICENSE
|
||||
-->
|
||||
|
||||
<manifest package="net.mamoe.mirai" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
</manifest>
|
@ -18,13 +18,10 @@ plugins {
|
||||
id("net.mamoe.kotlin-jvm-blocking-bridge")
|
||||
`maven-publish`
|
||||
id("com.jfrog.bintray")
|
||||
java
|
||||
}
|
||||
|
||||
description = "Mirai Protocol implementation for QQ Android"
|
||||
|
||||
val isAndroidSDKAvailable: Boolean by project
|
||||
|
||||
afterEvaluate {
|
||||
tasks.getByName("compileKotlinCommon").enabled = false
|
||||
tasks.getByName("compileTestKotlinCommon").enabled = false
|
||||
@ -37,9 +34,13 @@ kotlin {
|
||||
explicitApi()
|
||||
|
||||
if (isAndroidSDKAvailable) {
|
||||
apply(from = rootProject.file("gradle/android.gradle"))
|
||||
android("android") {
|
||||
publishAllLibraryVariants()
|
||||
// apply(from = rootProject.file("gradle/android.gradle"))
|
||||
// android("android") {
|
||||
// publishAllLibraryVariants()
|
||||
// }
|
||||
jvm("android") {
|
||||
attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.androidJvm)
|
||||
// publishAllLibraryVariants()
|
||||
}
|
||||
} else {
|
||||
printAndroidNotInstalled()
|
||||
@ -58,7 +59,7 @@ kotlin {
|
||||
|
||||
sourceSets.apply {
|
||||
|
||||
commonMain {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
api(project(":mirai-core-api"))
|
||||
implementation(project(":mirai-core-utils"))
|
||||
@ -81,12 +82,13 @@ kotlin {
|
||||
}
|
||||
|
||||
if (isAndroidSDKAvailable) {
|
||||
androidMain {
|
||||
val androidMain by getting {
|
||||
dependsOn(commonMain)
|
||||
dependencies {
|
||||
compileOnly(`android-runtime`)
|
||||
}
|
||||
}
|
||||
|
||||
androidTest {
|
||||
val androidTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test", Versions.kotlinCompiler))
|
||||
implementation(kotlin("test-junit", Versions.kotlinCompiler))
|
||||
@ -96,22 +98,35 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
jvmMain {
|
||||
val jvmMain by getting {
|
||||
dependencies {
|
||||
implementation("org.bouncycastle:bcprov-jdk15on:1.64")
|
||||
// api(kotlinx("coroutines-debug", Versions.coroutines))
|
||||
}
|
||||
}
|
||||
|
||||
jvmTest {
|
||||
val jvmTest by getting {
|
||||
dependencies {
|
||||
implementation("org.pcap4j:pcap4j-distribution:1.8.2")
|
||||
// implementation("net.mamoe:mirai-login-solver-selenium:1.0-dev-14")
|
||||
// implementation("net.mamoe:mirai-login-solver-selenium:1.0-dev-14")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("checkAndroidApiLevel") {
|
||||
doFirst {
|
||||
androidutil.AndroidApiLevelCheck.check(
|
||||
buildDir.resolve("classes/kotlin/android/main"),
|
||||
project.property("mirai.android.target.api.level")!!.toString().toInt(),
|
||||
project
|
||||
)
|
||||
}
|
||||
group = "verification"
|
||||
this.mustRunAfter("androidMainClasses")
|
||||
}
|
||||
tasks.getByName("androidTest").dependsOn("checkAndroidApiLevel")
|
||||
|
||||
fun org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler.implementation1(dependencyNotation: String) =
|
||||
implementation(dependencyNotation) {
|
||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib")
|
||||
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2019-2021 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/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import net.mamoe.mirai.utils.md5
|
||||
import java.security.*
|
||||
import java.security.spec.ECGenParameterSpec
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
import javax.crypto.KeyAgreement
|
||||
|
||||
|
||||
@Suppress("ACTUAL_WITHOUT_EXPECT")
|
||||
internal actual typealias ECDHPrivateKey = PrivateKey
|
||||
@Suppress("ACTUAL_WITHOUT_EXPECT")
|
||||
internal actual typealias ECDHPublicKey = PublicKey
|
||||
|
||||
internal actual class ECDHKeyPairImpl(
|
||||
private val delegate: KeyPair
|
||||
) : ECDHKeyPair {
|
||||
override val privateKey: ECDHPrivateKey get() = delegate.private
|
||||
override val publicKey: ECDHPublicKey get() = delegate.public
|
||||
|
||||
override val initialShareKey: ByteArray by lazy { ECDH.calculateShareKey(privateKey, initialPublicKey) }
|
||||
}
|
||||
|
||||
internal actual fun ECDH() = ECDH(ECDH.generateKeyPair())
|
||||
|
||||
internal actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
|
||||
actual companion object {
|
||||
private const val curveName = "secp192k1" // p-256
|
||||
|
||||
actual val isECDHAvailable: Boolean
|
||||
|
||||
init {
|
||||
isECDHAvailable = kotlin.runCatching {
|
||||
fun testECDH() {
|
||||
ECDHKeyPairImpl(
|
||||
KeyPairGenerator.getInstance("ECDH")
|
||||
.also { it.initialize(ECGenParameterSpec(curveName)) }
|
||||
.genKeyPair()).let {
|
||||
calculateShareKey(it.privateKey, it.publicKey)
|
||||
}
|
||||
}
|
||||
|
||||
if (kotlin.runCatching { testECDH() }.isSuccess) {
|
||||
return@runCatching
|
||||
}
|
||||
|
||||
testECDH()
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}.isSuccess
|
||||
}
|
||||
|
||||
actual fun generateKeyPair(): ECDHKeyPair {
|
||||
if (!isECDHAvailable) {
|
||||
return ECDHKeyPair.DefaultStub
|
||||
}
|
||||
return ECDHKeyPairImpl(
|
||||
KeyPairGenerator.getInstance("ECDH")
|
||||
.also { it.initialize(ECGenParameterSpec(curveName)) }
|
||||
.genKeyPair())
|
||||
}
|
||||
|
||||
actual fun calculateShareKey(
|
||||
privateKey: ECDHPrivateKey,
|
||||
publicKey: ECDHPublicKey
|
||||
): ByteArray {
|
||||
val instance = KeyAgreement.getInstance("ECDH", "BC")
|
||||
instance.init(privateKey)
|
||||
instance.doPhase(publicKey, true)
|
||||
return instance.generateSecret().md5()
|
||||
}
|
||||
|
||||
actual fun constructPublicKey(key: ByteArray): ECDHPublicKey {
|
||||
return KeyFactory.getInstance("EC", "BC").generatePublic(X509EncodedKeySpec(key))
|
||||
}
|
||||
}
|
||||
|
||||
actual fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray {
|
||||
return calculateShareKey(keyPair.privateKey, peerPublicKey)
|
||||
}
|
||||
|
||||
actual override fun toString(): String {
|
||||
return "ECDH(keyPair=$keyPair)"
|
||||
}
|
||||
}
|
@ -29,6 +29,8 @@ include(":mirai-core")
|
||||
include(":mirai-core-all")
|
||||
|
||||
include(":binary-compatibility-validator")
|
||||
include(":binary-compatibility-validator-android")
|
||||
project(":binary-compatibility-validator-android").projectDir = file("binary-compatibility-validator/android")
|
||||
include(":ci-release-helper")
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user