1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-04-17 09:09:23 +08:00

Simplify platform structure: merge jvmMain into commonMain

This commit is contained in:
Him188 2020-11-30 23:30:57 +08:00
parent d0d73d5285
commit eafca6d4ed
36 changed files with 293 additions and 420 deletions

View File

@ -75,8 +75,8 @@ kotlin {
api1(`kotlinx-serialization-core`)
api1(`kotlinx-serialization-json`)
implementation1(`kotlinx-serialization-protobuf`)
api1(`kotlinx-io`)
api1(`kotlinx-coroutines-io`)
api1(`kotlinx-io-jvm`)
api1(`kotlinx-coroutines-io-jvm`)
api(`kotlinx-coroutines-core`)
implementation1(`kotlinx-atomicfu`)
@ -84,32 +84,21 @@ kotlin {
api1(`ktor-client-cio`)
api1(`ktor-client-core`)
api1(`ktor-network`)
compileOnly(`log4j-api`)
compileOnly(slf4j)
}
}
if (isAndroidSDKAvailable) {
androidMain {
dependencies {
api(kotlin("reflect"))
api1(`kotlinx-io-jvm`)
api1(`kotlinx-coroutines-io-jvm`)
api1(`ktor-client-android`)
}
}
}
val jvmMain by getting {
dependencies {
api(kotlin("reflect"))
compileOnly(`log4j-api`)
compileOnly(slf4j)
api1(`kotlinx-io-jvm`)
api1(`kotlinx-coroutines-io-jvm`)
}
}
val jvmMain by getting
val jvmTest by getting {
dependencies {

View File

@ -20,13 +20,12 @@ import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.User
import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.event.internal.MiraiAtomicBoolean
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.message.action.Nudge
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.SinceMirai
import net.mamoe.mirai.utils.internal.runBlocking
import kotlin.jvm.*
import java.util.concurrent.atomic.AtomicBoolean
/**
@ -90,7 +89,7 @@ public data class NewFriendRequestEvent internal constructor(
public val fromNick: String
) : BotEvent, Packet, AbstractEvent() {
@JvmField
internal val responded: MiraiAtomicBoolean = MiraiAtomicBoolean(false)
internal val responded: AtomicBoolean = AtomicBoolean(false)
/**
* @return 申请人来自的群. 当申请人来自其他途径申请时为 `null`

View File

@ -22,12 +22,12 @@ import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.event.internal.MiraiAtomicBoolean
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.message.action.Nudge
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.SinceMirai
import net.mamoe.mirai.utils.internal.runBlocking
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.internal.LowPriorityInOverloadResolution
import kotlin.jvm.*
@ -339,7 +339,7 @@ public data class BotInvitedJoinGroupRequestEvent internal constructor(
public val invitor: Friend get() = this.bot.getFriend(invitorId)
@JvmField
internal val responded: MiraiAtomicBoolean = MiraiAtomicBoolean(false)
internal val responded: AtomicBoolean = AtomicBoolean(false)
@JvmSynthetic
public suspend fun accept(): Unit = Mirai.acceptInvitedJoinGroupRequest(this)
@ -387,7 +387,7 @@ public data class MemberJoinRequestEvent internal constructor(
@JvmField
@PublishedApi
internal val responded: MiraiAtomicBoolean = MiraiAtomicBoolean(false)
internal val responded: AtomicBoolean = AtomicBoolean(false)
@JvmSynthetic
public suspend fun accept(): Unit = Mirai.acceptMemberJoinRequest(this)

View File

@ -17,9 +17,9 @@ import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.PlannedRemoval
import java.util.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
import kotlin.jvm.JvmField
import kotlin.reflect.KClass
@ -112,16 +112,21 @@ internal class ListenerRegistry(
val type: KClass<out Event>
)
internal expect object GlobalEventListeners {
operator fun get(priority: Listener.EventPriority): LockFreeLinkedList<ListenerRegistry>
}
@PublishedApi
internal expect class MiraiAtomicBoolean(initial: Boolean) {
internal object GlobalEventListeners {
private val ALL_LEVEL_REGISTRIES: Map<Listener.EventPriority, LockFreeLinkedList<ListenerRegistry>>
fun compareAndSet(expect: Boolean, update: Boolean): Boolean
init {
val map =
EnumMap<Listener.EventPriority, LockFreeLinkedList<ListenerRegistry>>(Listener.EventPriority::class.java)
Listener.EventPriority.values().forEach {
map[it] = LockFreeLinkedList()
}
this.ALL_LEVEL_REGISTRIES = map
}
var value: Boolean
operator fun get(priority: Listener.EventPriority): LockFreeLinkedList<ListenerRegistry> =
ALL_LEVEL_REGISTRIES[priority]!!
}

View File

@ -11,16 +11,18 @@ package net.mamoe.mirai.event.internal
import java.util.concurrent.atomic.AtomicBoolean
internal actual class MiraiAtomicBoolean actual constructor(initial: Boolean) {
@PublishedApi
internal class MiraiAtomicBoolean constructor(initial: Boolean) {
private val delegate: AtomicBoolean = AtomicBoolean(initial)
actual fun compareAndSet(expect: Boolean, update: Boolean): Boolean {
fun compareAndSet(expect: Boolean, update: Boolean): Boolean {
return delegate.compareAndSet(expect, update)
}
actual var value: Boolean
var value: Boolean
get() = delegate.get()
set(value) {
delegate.set(value)
}
}
}

View File

@ -11,13 +11,26 @@ package net.mamoe.mirai.utils
import kotlinx.io.core.toByteArray
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.protobuf.ProtoBuf
import kotlinx.serialization.protobuf.ProtoNumber
import net.mamoe.mirai.utils.internal.getRandomByteArray
import net.mamoe.mirai.utils.internal.getRandomIntString
import net.mamoe.mirai.utils.internal.getRandomString
import net.mamoe.mirai.utils.internal.md5
import kotlin.jvm.JvmStatic
import java.io.File
/**
* 加载一个设备信息. 若文件不存在或为空则随机并创建一个设备信息保存.
*/
public fun File.loadAsDeviceInfo(json: Json): DeviceInfo {
if (!this.exists() || this.length() == 0L) {
return DeviceInfo.random().also {
this.writeText(json.encodeToString(DeviceInfo.serializer(), it))
}
}
return json.decodeFromString(DeviceInfo.serializer(), this.readText())
}
@Serializable
public class DeviceInfo(

View File

@ -25,7 +25,7 @@ public class SingleFileLogger @JvmOverloads constructor(
identity: String,
file: File = File("$identity-$currentDate.log")
) :
PlatformLogger(identity, { file.appendText(it + "\n") }, false) {
PlatformLogger(identity, { file.appendText(it + "\n") }) {
init {
file.createNewFile()
@ -55,7 +55,7 @@ public class DirectoryLogger @JvmOverloads constructor(
}
private fun checkOutdated() {
val current = currentTimeMillis
val current = currentTimeMillis()
directory.walk().filter(File::isFile).filter { current - it.lastModified() > retain }.forEach {
it.delete()
}

View File

@ -9,13 +9,29 @@
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.network.LoginFailedException
import net.mamoe.mirai.network.NoStandardInputForCaptchaException
import utils.SwingSolver
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
/**
* 验证码, 设备锁解决器
*/
public expect abstract class LoginSolver {
public abstract class LoginSolver {
/**
* 处理图片验证码.
* 返回 null 以表示无法处理验证码, 将会刷新验证码或重试登录.
@ -46,6 +62,189 @@ public expect abstract class LoginSolver {
public abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String?
public companion object {
public val Default: LoginSolver
public val Default: LoginSolver =
DefaultLoginSolver({ readLine() ?: throw NoStandardInputForCaptchaException(null) })
}
}
}
/**
* 自动选择 [SwingSolver] [StandardCharImageLoginSolver]
*/
@MiraiExperimentalApi
public class DefaultLoginSolver(
public val input: suspend () -> String,
public val overrideLogger: MiraiLogger? = null
) : LoginSolver() {
private val delegate: LoginSolver
init {
if (WindowHelperJvm.isDesktopSupported) {
delegate = SwingSolver
} else {
delegate = StandardCharImageLoginSolver(input, overrideLogger)
}
}
override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? {
return delegate.onSolvePicCaptcha(bot, data)
}
override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? {
return delegate.onSolveSliderCaptcha(bot, url)
}
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? {
return delegate.onSolveUnsafeDeviceLoginVerify(bot, url)
}
}
/**
* 使用字符图片展示验证码, 使用 [input] 获取输入, 使用 [overrideLogger] 输出
*/
@MiraiExperimentalApi
public class StandardCharImageLoginSolver(
input: suspend () -> String,
/**
* `null` 时使用 [Bot.logger]
*/
private val overrideLogger: MiraiLogger? = null
) : LoginSolver() {
private val input: suspend () -> String = suspend {
withContext(Dispatchers.IO) { input() }
}
override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? = loginSolverLock.withLock {
val logger = overrideLogger ?: bot.logger
val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() }
withContext(Dispatchers.IO) {
tempFile.createNewFile()
logger.info("需要图片验证码登录, 验证码为 4 字母")
try {
tempFile.writeChannel().apply { writeFully(data); close() }
logger.info("将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}")
} catch (e: Exception) {
logger.info("无法写出验证码文件(${e.message}), 请尝试查看以上字符图片")
}
tempFile.inputStream().use {
try {
val img = ImageIO.read(it)
if (img == null) {
logger.info("无法创建字符图片. 请查看文件")
} else {
logger.info("\n" + img.createCharImg())
}
} catch (throwable: Throwable) {
logger.info("创建字符图片时出错($throwable)。请查看文件")
}
}
}
logger.info("请输入 4 位字母验证码. 若要更换验证码, 请直接回车")
return input().takeUnless { it.isEmpty() || it.length != 4 }.also {
logger.info("正在提交[$it]中...")
}
}
override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String = loginSolverLock.withLock {
val logger = overrideLogger ?: bot.logger
logger.info("需要滑动验证码")
logger.info("请在任意浏览器中打开以下链接并完成验证码. ")
logger.info("完成后请输入任意字符 ")
logger.info(url)
return input().also {
logger.info("正在提交中...")
}
}
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? = loginSolverLock.withLock {
val logger = overrideLogger ?: bot.logger
logger.info("需要进行账户安全认证")
logger.info("该账户有[设备锁]/[不常用登录地点]/[不常用设备登录]的问题")
logger.info("完成以下账号认证即可成功登录|理论本认证在mirai每个账户中最多出现1次")
logger.info("请将该链接在QQ浏览器中打开并完成认证, 成功后输入任意字符")
logger.info("这步操作将在后续的版本中优化")
logger.info(url)
return input().also {
logger.info("正在提交中...")
}
}
}
///////////////////////////////
//////////////// internal
///////////////////////////////
internal fun BotConfiguration.getFileBasedDeviceInfoSupplier(filename: String): (Context) -> DeviceInfo {
return {
File(filename).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")
}
}
}

View File

@ -14,9 +14,6 @@
package net.mamoe.mirai.utils
import net.mamoe.mirai.Bot
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
import kotlin.jvm.JvmOverloads
/**
@ -229,7 +226,13 @@ public inline fun MiraiLogger.error(lazyMessage: () -> String?, e: Throwable?) {
*
* @see DefaultLogger
*/
public expect open class PlatformLogger @JvmOverloads constructor(identity: String? = "Mirai") : MiraiLoggerPlatformBase
public expect open class PlatformLogger 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")
}
/**

View File

@ -12,10 +12,7 @@
package net.mamoe.mirai.utils
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
/**
* 图片文件过大
*/ // 不要删除多平台结构, 这是 kotlin 的 bug
public expect class OverFileSizeMaxException() : IllegalStateException
public class OverFileSizeMaxException : IllegalStateException()

View File

@ -7,9 +7,13 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.utils
package utils
import net.mamoe.mirai.Bot
import net.mamoe.mirai.utils.HyperLinkLabel
import net.mamoe.mirai.utils.LoginSolver
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.openWindow
import java.awt.Desktop
import java.net.URI
import javax.imageio.ImageIO

View File

@ -12,9 +12,6 @@
package net.mamoe.mirai.utils
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
import kotlin.math.floor
import kotlin.time.Duration
import kotlin.time.DurationUnit
@ -23,11 +20,11 @@ import kotlin.time.ExperimentalTime
/**
* 时间戳
*/
public expect val currentTimeMillis: Long
public fun currentTimeMillis(): Long = System.currentTimeMillis()
@get:JvmSynthetic
public inline val currentTimeSeconds: Long
get() = currentTimeMillis / 1000
get() = currentTimeMillis() / 1000
// 临时使用, 待 Kotlin Duration 稳定后使用 Duration.

View File

@ -1,14 +0,0 @@
/*
* 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.utils.internal
internal expect fun ByteArray.asReusableInput(): ReusableInput
internal fun asReusableInput0(input: ByteArray): ReusableInput = input.asReusableInput()

View File

@ -20,9 +20,11 @@ import java.io.ByteArrayInputStream
import java.io.File
import java.io.InputStream
internal fun asReusableInput0(input: ByteArray): ReusableInput = input.asReusableInput()
internal const val DEFAULT_REUSABLE_INPUT_BUFFER_SIZE = 8192
internal actual fun ByteArray.asReusableInput(): ReusableInput {
internal fun ByteArray.asReusableInput(): ReusableInput {
return object : ReusableInput {
override val md5: ByteArray = md5()
override val size: Long get() = this@asReusableInput.size.toLongUnsigned()

View File

@ -1,32 +0,0 @@
/*
* 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.event.internal
import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.utils.LockFreeLinkedList
import java.util.*
internal actual object GlobalEventListeners {
private val ALL_LEVEL_REGISTRIES: Map<Listener.EventPriority, LockFreeLinkedList<ListenerRegistry>>
init {
val map =
EnumMap<Listener.EventPriority, LockFreeLinkedList<ListenerRegistry>>(Listener.EventPriority::class.java)
Listener.EventPriority.values().forEach {
map[it] = LockFreeLinkedList()
}
this.ALL_LEVEL_REGISTRIES = map
}
actual operator fun get(priority: Listener.EventPriority): LockFreeLinkedList<ListenerRegistry> =
ALL_LEVEL_REGISTRIES[priority]!!
}

View File

@ -1,25 +0,0 @@
/*
* 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.utils
import kotlinx.serialization.json.Json
import java.io.File
/**
* 加载一个设备信息. 若文件不存在或为空则随机并创建一个设备信息保存.
*/
public fun File.loadAsDeviceInfo(json: Json): DeviceInfo {
if (!this.exists() || this.length() == 0L) {
return DeviceInfo.random().also {
this.writeText(json.encodeToString(DeviceInfo.serializer(), it))
}
}
return json.decodeFromString(DeviceInfo.serializer(), this.readText())
}

View File

@ -1,227 +0,0 @@
/*
* 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.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 kotlinx.io.core.use
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.NoStandardInputForCaptchaException
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
/**
* 自动选择 [SwingSolver] [StandardCharImageLoginSolver]
*/
@MiraiExperimentalApi
public class DefaultLoginSolver(
public val input: suspend () -> String,
public val overrideLogger: MiraiLogger? = null
) : LoginSolver() {
private val delegate: LoginSolver
init {
if (WindowHelperJvm.isDesktopSupported) {
delegate = SwingSolver
} else {
delegate = StandardCharImageLoginSolver(input, overrideLogger)
}
}
override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? {
return delegate.onSolvePicCaptcha(bot, data)
}
override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? {
return delegate.onSolveSliderCaptcha(bot, url)
}
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? {
return delegate.onSolveUnsafeDeviceLoginVerify(bot, url)
}
}
/**
* 使用字符图片展示验证码, 使用 [input] 获取输入, 使用 [overrideLogger] 输出
*/
@MiraiExperimentalApi
public class StandardCharImageLoginSolver(
input: suspend () -> String,
/**
* `null` 时使用 [Bot.logger]
*/
private val overrideLogger: MiraiLogger? = null
) : LoginSolver() {
private val input: suspend () -> String = suspend {
withContext(Dispatchers.IO) { input() }
}
override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? = loginSolverLock.withLock {
val logger = overrideLogger ?: bot.logger
val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() }
withContext(Dispatchers.IO) {
tempFile.createNewFile()
logger.info("需要图片验证码登录, 验证码为 4 字母")
try {
tempFile.writeChannel().apply { writeFully(data); close() }
logger.info("将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}")
} catch (e: Exception) {
logger.info("无法写出验证码文件(${e.message}), 请尝试查看以上字符图片")
}
tempFile.inputStream().use {
try {
val img = ImageIO.read(it)
if (img == null) {
logger.info("无法创建字符图片. 请查看文件")
} else {
logger.info("\n" + img.createCharImg())
}
} catch (throwable: Throwable) {
logger.info("创建字符图片时出错($throwable)。请查看文件")
}
}
}
logger.info("请输入 4 位字母验证码. 若要更换验证码, 请直接回车")
return input().takeUnless { it.isEmpty() || it.length != 4 }.also {
logger.info("正在提交[$it]中...")
}
}
override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? = loginSolverLock.withLock {
val logger = overrideLogger ?: bot.logger
logger.info("需要滑动验证码")
logger.info("请在任意浏览器中打开以下链接并完成验证码. ")
logger.info("完成后请输入任意字符 ")
logger.info(url)
return input().also {
logger.info("正在提交中...")
}
}
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? = loginSolverLock.withLock {
val logger = overrideLogger ?: bot.logger
logger.info("需要进行账户安全认证")
logger.info("该账户有[设备锁]/[不常用登录地点]/[不常用设备登录]的问题")
logger.info("完成以下账号认证即可成功登录|理论本认证在mirai每个账户中最多出现1次")
logger.info("请将该链接在QQ浏览器中打开并完成认证, 成功后输入任意字符")
logger.info("这步操作将在后续的版本中优化")
logger.info(url)
return input().also {
logger.info("正在提交中...")
}
}
}
/**
* 验证码, 设备锁解决器
*/
public actual abstract class LoginSolver {
public actual abstract suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String?
public actual abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String?
public actual abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String?
public actual companion object {
public actual val Default: LoginSolver =
DefaultLoginSolver({ readLine() ?: throw NoStandardInputForCaptchaException(null) })
}
}
///////////////////////////////
//////////////// internal
///////////////////////////////
internal fun BotConfiguration.getFileBasedDeviceInfoSupplier(filename: String): ((Context) -> DeviceInfo)? {
return {
File(filename).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")
}
}
}

View File

@ -1,15 +0,0 @@
/*
* 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.utils
/**
* 图片文件过大
*/
public actual class OverFileSizeMaxException : IllegalStateException()

View File

@ -13,8 +13,6 @@
package net.mamoe.mirai.utils
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
@ -44,7 +42,7 @@ import java.util.*
* @see SingleFileLogger 使用单一文件记录日志
* @see DirectoryLogger 在一个目录中按日期存放文件记录日志, 自动清理过期日志
*/
public actual open class PlatformLogger @JvmOverloads constructor(
public actual open class PlatformLogger constructor(
public override val identity: String? = "Mirai",
/**
* 日志输出. 不会自动添加换行
@ -53,6 +51,7 @@ public actual open class PlatformLogger @JvmOverloads constructor(
public val isColored: Boolean = true
) : MiraiLoggerPlatformBase() {
public actual constructor(identity: String?) : this(identity, ::println)
public actual constructor(identity: String?, output: (String) -> Unit) : this(identity, output, true)
/**
* 输出一条日志. [message] 末尾可能不带换行符.

View File

@ -1,20 +0,0 @@
/*
* 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:JvmMultifileClass
@file:JvmName("Utils")
@file:Suppress("EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE")
package net.mamoe.mirai.utils
/**
* 时间戳
*/
public actual val currentTimeMillis: Long
get() = System.currentTimeMillis()

View File

@ -44,7 +44,7 @@ import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.currentTimeSeconds
import kotlin.jvm.JvmSynthetic
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.math.absoluteValue
import kotlin.random.Random
@ -63,7 +63,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
@OptIn(LowLevelApi::class)
override suspend fun acceptNewFriendRequest(event: NewFriendRequestEvent) {
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
check(event.responded.compareAndSet(expect = false, update = true)) {
check(event.responded.compareAndSet(false, true)) {
"the request $this has already been responded"
}
@ -83,7 +83,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
override suspend fun rejectNewFriendRequest(event: NewFriendRequestEvent, blackList: Boolean) {
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
check(event.responded.compareAndSet(expect = false, update = true)) {
check(event.responded.compareAndSet(false, true)) {
"the request $event has already been responded"
}
@ -105,7 +105,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
@Suppress("DuplicatedCode")
checkGroupPermission(event.bot, event.group) { event::class.simpleName ?: "<anonymous class>" }
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
check(event.responded.compareAndSet(expect = false, update = true)) {
check(event.responded.compareAndSet(false, true)) {
"the request $this has already been responded"
}
@ -128,7 +128,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
override suspend fun rejectMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean, message: String) {
checkGroupPermission(event.bot, event.group) { event::class.simpleName ?: "<anonymous class>" }
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
check(event.responded.compareAndSet(expect = false, update = true)) {
check(event.responded.compareAndSet(false, true)) {
"the request $this has already been responded"
}
@ -164,7 +164,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
override suspend fun ignoreMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean) {
checkGroupPermission(event.bot, event.group) { event::class.simpleName ?: "<anonymous class>" }
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
check(event.responded.compareAndSet(expect = false, update = true)) {
check(event.responded.compareAndSet(false, true)) {
"the request $this has already been responded"
}
@ -188,7 +188,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
private suspend fun solveInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent, accept: Boolean) {
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
check(event.responded.compareAndSet(expect = false, update = true)) {
check(event.responded.compareAndSet(false, true)) {
"the request $this has already been responded"
}
@ -260,7 +260,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
source.ensureSequenceIdAvailable()
@Suppress("BooleanLiteralArgument", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") // false positive
check(!source.isRecalledOrPlanned.value && source.isRecalledOrPlanned.compareAndSet(false, true)) {
check(!source.isRecalledOrPlanned.get() && source.isRecalledOrPlanned.compareAndSet(false, true)) {
"$source had already been recalled."
}
@ -830,8 +830,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
override val internalId: Int = internalId
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
override var isRecalledOrPlanned: net.mamoe.mirai.event.internal.MiraiAtomicBoolean =
net.mamoe.mirai.event.internal.MiraiAtomicBoolean(false)
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
override fun toJceData(): ImMsgBody.SourceMsg {
return ImMsgBody.SourceMsg(

View File

@ -14,7 +14,6 @@ package net.mamoe.mirai.internal.message
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.event.internal.MiraiAtomicBoolean
import net.mamoe.mirai.internal.contact.GroupImpl
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
@ -27,6 +26,7 @@ import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.OnlineMessageSource
import java.util.concurrent.atomic.AtomicBoolean
internal interface MessageSourceInternal {
val sequenceId: Int
@ -35,7 +35,7 @@ internal interface MessageSourceInternal {
@Deprecated("don't use this internally. Use sequenceId or random instead.", level = DeprecationLevel.ERROR)
val id: Int
val isRecalledOrPlanned: MiraiAtomicBoolean
val isRecalledOrPlanned: AtomicBoolean
fun toJceData(): ImMsgBody.SourceMsg
}
@ -67,7 +67,7 @@ internal class MessageSourceFromFriendImpl(
val msg: MsgComm.Msg
) : OnlineMessageSource.Incoming.FromFriend(), MessageSourceInternal {
override val sequenceId: Int get() = msg.msgHead.msgSeq
override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false)
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
override val id: Int get() = sequenceId// msg.msgBody.richText.attr!!.random
override val internalId: Int get() = msg.msgBody.richText.attr!!.random
override val time: Int get() = msg.msgHead.msgTime
@ -121,7 +121,7 @@ internal class MessageSourceFromTempImpl(
) : OnlineMessageSource.Incoming.FromTemp(), MessageSourceInternal {
override val sequenceId: Int get() = msg.msgHead.msgSeq
override val internalId: Int get() = msg.msgBody.richText.attr!!.random
override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false)
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
override val id: Int get() = sequenceId//
override val time: Int get() = msg.msgHead.msgTime
override val originalMessage: MessageChain by lazy { msg.toMessageChain(bot, 0, false) }
@ -135,7 +135,7 @@ internal data class MessageSourceFromGroupImpl(
override val bot: Bot,
private val msg: MsgComm.Msg
) : OnlineMessageSource.Incoming.FromGroup(), MessageSourceInternal {
override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false)
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
override val sequenceId: Int get() = msg.msgHead.msgSeq
override val internalId: Int get() = msg.msgBody.richText.attr!!.random
override val id: Int get() = sequenceId

View File

@ -12,7 +12,6 @@
package net.mamoe.mirai.internal.message
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.internal.MiraiAtomicBoolean
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.internal.network.protocol.data.proto.SourceMsg
@ -20,6 +19,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.OfflineMessageSource
import java.util.concurrent.atomic.AtomicBoolean
internal class OfflineMessageSourceImplByMsg(
@ -47,7 +47,7 @@ internal class OfflineMessageSourceImplByMsg(
override val sequenceId: Int
get() = delegate.msgHead.msgSeq
override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false)
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
override fun toJceData(): ImMsgBody.SourceMsg {
return ImMsgBody.SourceMsg(
@ -72,7 +72,7 @@ internal class OfflineMessageSourceImplBySourceMsg(
) : OfflineMessageSource(), MessageSourceInternal {
override val kind: Kind get() = if (delegate.srcMsg == null) Kind.GROUP else Kind.FRIEND
override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false)
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
override val sequenceId: Int
get() = delegate.origSeqs.firstOrNull() ?: error("cannot find sequenceId")
override val internalId: Int

View File

@ -19,7 +19,6 @@ import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.event.asyncFromEventOrNull
import net.mamoe.mirai.event.internal.MiraiAtomicBoolean
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.internal.network.protocol.data.proto.SourceMsg
@ -28,6 +27,7 @@ import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.OnlineMessageSource
import java.util.concurrent.atomic.AtomicBoolean
private fun <T> T.toJceDataImpl(): ImMsgBody.SourceMsg
@ -81,7 +81,7 @@ internal class MessageSourceToFriendImpl(
get() = sender
override val id: Int
get() = sequenceId
override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false)
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
private val jceData by lazy { toJceDataImpl() }
override fun toJceData(): ImMsgBody.SourceMsg = jceData
}
@ -98,7 +98,7 @@ internal class MessageSourceToTempImpl(
get() = sender
override val id: Int
get() = sequenceId
override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false)
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
private val jceData by lazy { toJceDataImpl() }
override fun toJceData(): ImMsgBody.SourceMsg = jceData
}
@ -115,7 +115,7 @@ internal class MessageSourceToGroupImpl(
get() = sequenceId
override val bot: Bot
get() = sender
override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false)
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
private val sequenceIdDeferred: Deferred<Int?> =
coroutineScope.asyncFromEventOrNull<SendGroupMessageReceipt, Int>(

View File

@ -35,7 +35,7 @@ internal fun BytePacketBuilder.t1(uin: Long, ip: ByteArray) {
writeShort(1) // _ip_ver
writeInt(Random.nextInt())
writeInt(uin.toInt())
writeInt(currentTimeMillis.toInt())
writeInt(currentTimeMillis().toInt())
writeFully(ip)
writeShort(0)
} shouldEqualsTo 20
@ -113,7 +113,7 @@ internal fun BytePacketBuilder.t106(
writeLong(uin)
}
writeInt(currentTimeMillis.toInt())
writeInt(currentTimeMillis().toInt())
writeFully(ByteArray(4)) // ip // no need to write actual ip
writeByte(isSavePassword.toByte())
writeFully(passwordMd5)
@ -555,7 +555,7 @@ internal fun BytePacketBuilder.t400(
writeFully(dpwd)
writeInt(appId.toInt())
writeInt(subAppId.toInt())
writeLong(currentTimeMillis)
writeLong(currentTimeMillis())
writeFully(randomSeed)
}
}

View File

@ -14,8 +14,6 @@ import kotlinx.atomicfu.atomic
import kotlinx.atomicfu.locks.reentrantLock
import kotlinx.atomicfu.locks.withLock
import net.mamoe.mirai.utils.currentTimeMillis
import kotlin.jvm.JvmField
import kotlin.jvm.Volatile
/**
@ -43,7 +41,7 @@ internal class AtomicResizeCacheList<E>(private val retention: Long) {
* No concurrency guaranteed on same [element].
*/
private fun add(element: E) {
val currentTime = currentTimeMillis
val currentTime = currentTimeMillis()
findAvailable@ while (true) {
for (cache in list) {
val instant = cache.time.value