Gather all platform-specified utilities into a MiraiPlatformUtils

This commit is contained in:
Him188 2020-03-08 21:14:41 +08:00
parent 9f988e7d04
commit 243b2ea731
20 changed files with 271 additions and 187 deletions

View File

@ -78,6 +78,7 @@ internal class QQImpl(
return MessageReceipt(source, this, null) return MessageReceipt(source, this, null)
} }
@OptIn(MiraiInternalAPI::class)
override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage = try { override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage = try {
if (BeforeImageUploadEvent(this, image).broadcast().isCancelled) { if (BeforeImageUploadEvent(this, image).broadcast().isCancelled) {
throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup") throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
@ -111,7 +112,7 @@ internal class QQImpl(
ImageUploadEvent.Succeed(this@QQImpl, image, it).broadcast() ImageUploadEvent.Succeed(this@QQImpl, image, it).broadcast()
} }
is LongConn.OffPicUp.Response.RequireUpload -> { is LongConn.OffPicUp.Response.RequireUpload -> {
Http.postImage( MiraiPlatformUtils.Http.postImage(
"0x6ff0070", "0x6ff0070",
bot.uin, bot.uin,
null, null,

View File

@ -222,7 +222,7 @@ internal abstract class QQAndroidBotBase constructor(
} }
override suspend fun openChannel(image: Image): ByteReadChannel { override suspend fun openChannel(image: Image): ByteReadChannel {
return Http.get<HttpResponse>(queryImageUrl(image)).content.toKotlinByteReadChannel() return MiraiPlatformUtils.Http.get<HttpResponse>(queryImageUrl(image)).content.toKotlinByteReadChannel()
} }
} }

View File

@ -9,18 +9,14 @@
package net.mamoe.mirai.qqandroid.message package net.mamoe.mirai.qqandroid.message
import kotlinx.io.core.buildPacket import kotlinx.io.core.*
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import kotlinx.io.core.readUInt
import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.MiraiDebugAPI import net.mamoe.mirai.utils.io.encodeToString
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.read import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.toByteArray import net.mamoe.mirai.utils.io.toByteArray
@ -222,7 +218,7 @@ private val atAllData = ImMsgBody.Elem(
) )
) )
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgBody.Elem> { internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgBody.Elem> {
val elements = mutableListOf<ImMsgBody.Elem>() val elements = mutableListOf<ImMsgBody.Elem>()
@ -243,6 +239,14 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB
elements.add(ImMsgBody.Elem(text = it.toJceData())) elements.add(ImMsgBody.Elem(text = it.toJceData()))
elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " "))) elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " ")))
} }
is RichMessage -> elements.add(
ImMsgBody.Elem(
richMsg = ImMsgBody.RichMsg(
serviceId = it.serviceId,
template1 = byteArrayOf(1) + MiraiPlatformUtils.zip(it.content.toByteArray())
)
)
)
is OfflineGroupImage -> elements.add(ImMsgBody.Elem(customFace = it.toJceData())) is OfflineGroupImage -> elements.add(ImMsgBody.Elem(customFace = it.toJceData()))
is OnlineGroupImageImpl -> elements.add(ImMsgBody.Elem(customFace = it.delegate)) is OnlineGroupImageImpl -> elements.add(ImMsgBody.Elem(customFace = it.delegate))
is OnlineFriendImageImpl -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate)) is OnlineFriendImageImpl -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate))
@ -400,6 +404,28 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChainBuilde
} }
} }
} }
it.richMsg != null -> {
when (it.richMsg.serviceId) {
60 -> message.add(
XMLMessage(
content = MiraiPlatformUtils.unzip(it.richMsg.template1, 1).encodeToString()
)
)
else -> {
@Suppress("DEPRECATION")
MiraiLogger.debug {
"unknown richMsg.serviceId: ${it.richMsg.serviceId}, content=${it.richMsg.template1.contentToString()}, \ntryUnzip=${
kotlin.runCatching {
MiraiPlatformUtils.unzip(it.richMsg.template1, 1).encodeToString()
}.getOrElse { "<failed>" }
}"
}
}
}
}
else -> {
println(it._miraiContentToString())
}
} }
} }

View File

@ -191,8 +191,9 @@ internal open class QQAndroidClient(
lateinit var t104: ByteArray lateinit var t104: ByteArray
} }
@OptIn(MiraiInternalAPI::class)
internal fun generateTgtgtKey(guid: ByteArray): ByteArray = internal fun generateTgtgtKey(guid: ByteArray): ByteArray =
md5(getRandomByteArray(16) + guid) MiraiPlatformUtils.md5(getRandomByteArray(16) + guid)
internal class ReserveUinInfo( internal class ReserveUinInfo(

View File

@ -26,6 +26,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.InternalSerializationApi
import net.mamoe.mirai.utils.MiraiPlatformUtils
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class) @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
internal fun createImageDataPacketSequence( // RequestDataTrans internal fun createImageDataPacketSequence( // RequestDataTrans
@ -77,7 +78,7 @@ internal fun createImageDataPacketSequence( // RequestDataTrans
dataoffset = offset, dataoffset = offset,
filesize = dataSize.toLong(), filesize = dataSize.toLong(),
serviceticket = uKey, serviceticket = uKey,
md5 = net.mamoe.mirai.utils.md5(chunkedInput.buffer, 0, chunkedInput.bufferSize), md5 = MiraiPlatformUtils.md5(chunkedInput.buffer, 0, chunkedInput.bufferSize),
fileMd5 = fileMd5, fileMd5 = fileMd5,
flag = 0, flag = 0,
rtcode = 0 rtcode = 0

View File

@ -342,7 +342,7 @@ internal object KnownPacketFactories {
1 -> { 1 -> {
input.discardExact(4) input.discardExact(4)
input.useBytes { data, length -> input.useBytes { data, length ->
data.unzip(length = length).let { MiraiPlatformUtils.unzip(data, 0, length).let {
val size = it.toInt() val size = it.toInt()
if (size == it.size || size == it.size + 4) { if (size == it.size || size == it.size + 4) {
it.toReadPacket(offset = 4) it.toReadPacket(offset = 4)

View File

@ -15,9 +15,10 @@ import kotlinx.io.core.toByteArray
import kotlinx.io.core.writeFully import kotlinx.io.core.writeFully
import net.mamoe.mirai.qqandroid.network.protocol.LoginType import net.mamoe.mirai.qqandroid.network.protocol.LoginType
import net.mamoe.mirai.qqandroid.utils.NetworkType import net.mamoe.mirai.qqandroid.utils.NetworkType
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiPlatformUtils
import net.mamoe.mirai.utils.currentTimeMillis import net.mamoe.mirai.utils.currentTimeMillis
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.md5
import kotlin.random.Random import kotlin.random.Random
/** /**
@ -76,6 +77,7 @@ fun BytePacketBuilder.t18(
} shouldEqualsTo 22 } shouldEqualsTo 22
} }
@OptIn(MiraiInternalAPI::class)
fun BytePacketBuilder.t106( fun BytePacketBuilder.t106(
appId: Long = 16L, appId: Long = 16L,
subAppId: Long = 537062845L, subAppId: Long = 537062845L,
@ -96,7 +98,7 @@ fun BytePacketBuilder.t106(
guid?.requireSize(16) guid?.requireSize(16)
writeShortLVPacket { writeShortLVPacket {
encryptAndWrite(md5(passwordMd5 + ByteArray(4) + (salt.takeIf { it != 0L } ?: uin).toInt().toByteArray())) { encryptAndWrite(MiraiPlatformUtils.md5(passwordMd5 + ByteArray(4) + (salt.takeIf { it != 0L } ?: uin).toInt().toByteArray())) {
writeShort(4)//TGTGTVer writeShort(4)//TGTGTVer
writeInt(Random.nextInt()) writeInt(Random.nextInt())
writeInt(5)//ssoVer writeInt(5)//ssoVer
@ -321,12 +323,13 @@ fun BytePacketBuilder.t144(
} }
} }
@OptIn(MiraiInternalAPI::class)
fun BytePacketBuilder.t109( fun BytePacketBuilder.t109(
androidId: ByteArray androidId: ByteArray
) { ) {
writeShort(0x109) writeShort(0x109)
writeShortLVPacket { writeShortLVPacket {
writeFully(md5(androidId)) writeFully(MiraiPlatformUtils.md5(androidId))
} shouldEqualsTo 16 } shouldEqualsTo 16
} }
@ -556,21 +559,23 @@ fun BytePacketBuilder.t400(
} }
} }
@OptIn(MiraiInternalAPI::class)
fun BytePacketBuilder.t187( fun BytePacketBuilder.t187(
macAddress: ByteArray macAddress: ByteArray
) { ) {
writeShort(0x187) writeShort(0x187)
writeShortLVPacket { writeShortLVPacket {
writeFully(md5(macAddress)) // may be md5 writeFully(MiraiPlatformUtils.md5(macAddress)) // may be md5
} }
} }
@OptIn(MiraiInternalAPI::class)
fun BytePacketBuilder.t188( fun BytePacketBuilder.t188(
androidId: ByteArray androidId: ByteArray
) { ) {
writeShort(0x188) writeShort(0x188)
writeShortLVPacket { writeShortLVPacket {
writeFully(md5(androidId)) writeFully(MiraiPlatformUtils.md5(androidId))
} shouldEqualsTo 16 } shouldEqualsTo 16
} }

View File

@ -23,9 +23,10 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.writeSsoPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.writeSsoPacket
import net.mamoe.mirai.qqandroid.utils.NetworkType import net.mamoe.mirai.qqandroid.utils.NetworkType
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiPlatformUtils
import net.mamoe.mirai.utils.io.encodeToString import net.mamoe.mirai.utils.io.encodeToString
import net.mamoe.mirai.utils.io.toReadPacket import net.mamoe.mirai.utils.io.toReadPacket
import net.mamoe.mirai.utils.localIpAddress
@Suppress("EnumEntryName") @Suppress("EnumEntryName")
internal enum class RegPushReason { internal enum class RegPushReason {
@ -89,6 +90,7 @@ internal class StatSvc {
private const val subAppId = 537062845L private const val subAppId = 537062845L
@OptIn(MiraiInternalAPI::class)
operator fun invoke( operator fun invoke(
client: QQAndroidClient, client: QQAndroidClient,
regPushReason: RegPushReason = RegPushReason.appRegister regPushReason: RegPushReason = RegPushReason.appRegister
@ -138,7 +140,7 @@ internal class StatSvc {
strOSVer = client.device.version.release.encodeToString(), strOSVer = client.device.version.release.encodeToString(),
uOldSSOIp = 0, uOldSSOIp = 0,
uNewSSOIp = localIpAddress().split(".").foldIndexed(0L) { index: Int, acc: Long, s: String -> uNewSSOIp = MiraiPlatformUtils.localIpAddress().split(".").foldIndexed(0L) { index: Int, acc: Long, s: String ->
acc or ((s.toLong() shl (index * 16))) acc or ((s.toLong() shl (index * 16)))
}, },
strVendorName = "MIUI", strVendorName = "MIUI",

View File

@ -84,7 +84,7 @@ internal class WtLogin {
t8(2052) t8(2052)
t104(client.t104) t104(client.t104)
t116(150470524, 66560) t116(150470524, 66560)
t401(md5(client.device.guid + "stMNokHgxZUGhsYp".toByteArray() + t402)) t401(MiraiPlatformUtils.md5(client.device.guid + "stMNokHgxZUGhsYp".toByteArray() + t402))
} }
} }
} }

View File

@ -44,6 +44,7 @@ fun Bitmap.toExternalImage(formatName: String = "gif"): ExternalImage {
/** /**
* 读取文件头识别图片属性, 然后构造 [ExternalImage] * 读取文件头识别图片属性, 然后构造 [ExternalImage]
*/ */
@OptIn(MiraiInternalAPI::class)
@Throws(IOException::class) @Throws(IOException::class)
fun File.toExternalImage(): ExternalImage { fun File.toExternalImage(): ExternalImage {
val input = BitmapFactory.decodeFile(this.absolutePath) val input = BitmapFactory.decodeFile(this.absolutePath)
@ -52,7 +53,7 @@ fun File.toExternalImage(): ExternalImage {
return ExternalImage( return ExternalImage(
width = input.width, width = input.width,
height = input.height, height = input.height,
md5 = this.inputStream().use { it.md5() }, md5 = this.inputStream().use { MiraiPlatformUtils.md5(it) },
imageFormat = this.nameWithoutExtension, imageFormat = this.nameWithoutExtension,
input = this.inputStream().asInput(IoBuffer.Pool), input = this.inputStream().asInput(IoBuffer.Pool),
inputSize = this.length(), inputSize = this.length(),

View File

@ -21,6 +21,8 @@ import kotlinx.serialization.Transient
import kotlinx.serialization.UnstableDefault import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration import kotlinx.serialization.json.JsonConfiguration
import net.mamoe.mirai.utils.MiraiPlatformUtils.localIpAddress
import net.mamoe.mirai.utils.MiraiPlatformUtils.md5
import java.io.File import java.io.File
/** /**
@ -102,6 +104,7 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() {
(context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager).connectionInfo.ssid.toByteArray() (context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager).connectionInfo.ssid.toByteArray()
}.getOrElse { byteArrayOf() } }.getOrElse { byteArrayOf() }
@OptIn(MiraiInternalAPI::class)
override val imsiMd5: ByteArray override val imsiMd5: ByteArray
@SuppressLint("HardwareIds") @SuppressLint("HardwareIds")
get() = md5(kotlin.runCatching { get() = md5(kotlin.runCatching {
@ -117,6 +120,8 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() {
(context.applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).deviceId (context.applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).deviceId
} }
}.getOrElse { "" } }.getOrElse { "" }
@OptIn(MiraiInternalAPI::class)
override val ipAddress: ByteArray get() = localIpAddress().split(".").map { it.toByte() }.takeIf { it.size == 4 }?.toByteArray() ?: byteArrayOf() override val ipAddress: ByteArray get() = localIpAddress().split(".").map { it.toByte() }.takeIf { it.size == 4 }?.toByteArray() ?: byteArrayOf()
override val androidId: ByteArray get() = Build.ID.toByteArray() override val androidId: ByteArray get() = Build.ID.toByteArray()
override val apn: ByteArray get() = "wifi".toByteArray() override val apn: ByteArray get() = "wifi".toByteArray()

View File

@ -10,7 +10,8 @@
package net.mamoe.mirai.utils.cryptor package net.mamoe.mirai.utils.cryptor
import android.annotation.SuppressLint import android.annotation.SuppressLint
import net.mamoe.mirai.utils.md5 import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiPlatformUtils.md5
import java.security.* import java.security.*
import java.security.spec.ECGenParameterSpec import java.security.spec.ECGenParameterSpec
import java.security.spec.X509EncodedKeySpec import java.security.spec.X509EncodedKeySpec
@ -71,6 +72,7 @@ actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
.genKeyPair()) .genKeyPair())
} }
@OptIn(MiraiInternalAPI::class)
actual fun calculateShareKey( actual fun calculateShareKey(
privateKey: ECDHPrivateKey, privateKey: ECDHPrivateKey,
publicKey: ECDHPublicKey publicKey: ECDHPublicKey

View File

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("NOTHING_TO_INLINE")
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
@ -15,88 +17,93 @@ import io.ktor.util.KtorExperimentalAPI
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.io.ByteArrayPool import net.mamoe.mirai.utils.io.ByteArrayPool
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.DataInput
import java.io.EOFException
import java.io.InputStream import java.io.InputStream
import java.net.InetAddress import java.net.InetAddress
import java.security.MessageDigest import java.security.MessageDigest
import java.util.zip.Deflater
import java.util.zip.Inflater import java.util.zip.Inflater
/**
* Ktor HttpClient. 不同平台使用不同引擎.
*/
@OptIn(KtorExperimentalAPI::class)
actual val Http: HttpClient
get() = HttpClient(CIO)
/**
* Localhost 解析
*/
actual fun localIpAddress(): String = runCatching {
InetAddress.getLocalHost().hostAddress
}.getOrElse { "192.168.1.123" }
/**
* MD5 算法
*
* @return 16 bytes
*/
actual fun md5(byteArray: ByteArray, offset: Int, length: Int): ByteArray =
MessageDigest.getInstance("MD5").apply { update(byteArray, offset, length) }.digest()
fun InputStream.md5(): ByteArray {
val digest = MessageDigest.getInstance("md5")
digest.reset()
this.readInSequence {
digest.update(it.toByte())
}
return digest.digest()
}
fun DataInput.md5(): ByteArray {
val digest = MessageDigest.getInstance("md5")
digest.reset()
val buffer = byteArrayOf(1)
while (true) {
try {
this.readFully(buffer)
} catch (e: EOFException) {
break
}
digest.update(buffer[0])
}
return digest.digest()
}
private inline fun InputStream.readInSequence(block: (Int) -> Unit) {
var read: Int
while (this.read().also { read = it } != -1) {
block(read)
}
}
@OptIn(MiraiInternalAPI::class)
actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray {
this.checkOffsetAndLength(offset, length)
if (length == 0) return ByteArray(0)
val inflater = Inflater()
inflater.reset()
ByteArrayOutputStream().use { output ->
inflater.setInput(this, offset, length)
ByteArrayPool.useInstance {
while (!inflater.finished()) {
output.write(it, 0, inflater.inflate(it))
}
}
inflater.end()
return output.toByteArray()
}
}
/** /**
* 时间戳 * 时间戳
*/ */
actual val currentTimeMillis: Long get() = System.currentTimeMillis() actual val currentTimeMillis: Long get() = System.currentTimeMillis()
@MiraiInternalAPI
actual object MiraiPlatformUtils {
actual fun unzip(data: ByteArray, offset: Int, length: Int): ByteArray {
data.checkOffsetAndLength(offset, length)
if (length == 0) return ByteArray(0)
val inflater = Inflater()
inflater.reset()
ByteArrayOutputStream().use { output ->
inflater.setInput(data, offset, length)
ByteArrayPool.useInstance {
while (!inflater.finished()) {
output.write(it, 0, inflater.inflate(it))
}
}
inflater.end()
return output.toByteArray()
}
}
actual fun zip(data: ByteArray, offset: Int, length: Int): ByteArray {
data.checkOffsetAndLength(offset, length)
if (length == 0) return ByteArray(0)
val inflater = Deflater()
inflater.reset()
ByteArrayOutputStream().use { output ->
inflater.setInput(data, offset, length)
ByteArrayPool.useInstance {
while (!inflater.finished()) {
output.write(it, 0, inflater.deflate(it))
}
}
inflater.end()
return output.toByteArray()
}
}
actual fun md5(data: ByteArray, offset: Int, length: Int): ByteArray {
data.checkOffsetAndLength(offset, length)
return MessageDigest.getInstance("MD5").apply { update(data, offset, length) }.digest()
}
actual inline fun md5(str: String): ByteArray = md5(str.toByteArray())
/**
* Ktor HttpClient. 不同平台使用不同引擎.
*/
@OptIn(KtorExperimentalAPI::class)
actual val Http: HttpClient
get() = HttpClient(CIO)
/**
* Localhost 解析
*/
actual fun localIpAddress(): String = runCatching {
InetAddress.getLocalHost().hostAddress
}.getOrElse { "192.168.1.123" }
fun md5(stream: InputStream): ByteArray {
val digest = MessageDigest.getInstance("md5")
digest.reset()
stream.readInSequence {
digest.update(it.toByte())
}
return digest.digest()
}
private inline fun InputStream.readInSequence(block: (Int) -> Unit) {
var read: Int
while (this.read().also { read = it } != -1) {
block(read)
}
}
}

View File

@ -14,7 +14,7 @@ package net.mamoe.mirai
import kotlinx.io.core.toByteArray import kotlinx.io.core.toByteArray
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.md5 import net.mamoe.mirai.utils.MiraiPlatformUtils
import kotlin.annotation.AnnotationTarget.* import kotlin.annotation.AnnotationTarget.*
@MiraiInternalAPI @MiraiInternalAPI
@ -28,7 +28,7 @@ data class BotAccount(
@MiraiInternalAPI @MiraiInternalAPI
val passwordMd5: ByteArray // md5 val passwordMd5: ByteArray // md5
) { ) {
constructor(id: Long, passwordPlainText: String) : this(id, md5(passwordPlainText.toByteArray())) constructor(id: Long, passwordPlainText: String) : this(id, MiraiPlatformUtils.md5(passwordPlainText.toByteArray()))
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true

View File

@ -94,6 +94,7 @@ abstract class DeviceInfo {
} }
} }
@OptIn(MiraiInternalAPI::class)
@Serializable @Serializable
class DeviceInfoData( class DeviceInfoData(
override val display: ByteArray, override val display: ByteArray,
@ -122,7 +123,8 @@ class DeviceInfoData(
@OptIn(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
override val ipAddress: ByteArray override val ipAddress: ByteArray
get() = localIpAddress().split(".").map { it.toUByte().toByte() }.takeIf { it.size == 4 }?.toByteArray() get() = MiraiPlatformUtils.localIpAddress().split(".").map { it.toUByte().toByte() }.takeIf { it.size == 4 }
?.toByteArray()
?: byteArrayOf() ?: byteArrayOf()
override val androidId: ByteArray get() = display override val androidId: ByteArray get() = display
@ -138,7 +140,9 @@ class DeviceInfoData(
/** /**
* Defaults "%4;7t>;28<fc.5*6".toByteArray() * Defaults "%4;7t>;28<fc.5*6".toByteArray()
*/ */
fun generateGuid(androidId: ByteArray, macAddress: ByteArray): ByteArray = md5(androidId + macAddress) @OptIn(MiraiInternalAPI::class)
fun generateGuid(androidId: ByteArray, macAddress: ByteArray): ByteArray =
MiraiPlatformUtils.md5(androidId + macAddress)
/* /*
fun DeviceInfo.toOidb0x769DeviceInfo() : Oidb0x769.DeviceInfo = Oidb0x769.DeviceInfo( fun DeviceInfo.toOidb0x769DeviceInfo() : Oidb0x769.DeviceInfo = Oidb0x769.DeviceInfo(

View File

@ -12,7 +12,6 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import kotlinx.io.core.toByteArray
/** /**
* 时间戳 * 时间戳
@ -21,30 +20,30 @@ expect val currentTimeMillis: Long
inline val currentTimeSeconds: Long get() = currentTimeMillis / 1000 inline val currentTimeSeconds: Long get() = currentTimeMillis / 1000
/** /**
* zip 压缩 * 仅供内部使用的工具类.
* 不写为扩展是为了避免污染命名空间.
*/ */
expect fun ByteArray.unzip(offset: Int = 0, length: Int = this.size - offset): ByteArray @MiraiInternalAPI
expect object MiraiPlatformUtils {
fun unzip(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray
/** fun zip(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray
* MD5 算法
*
* @return 16 bytes
*/
expect fun md5(byteArray: ByteArray, offset: Int = 0, length: Int = byteArray.size - offset): ByteArray
inline fun md5(str: String): ByteArray = md5(str.toByteArray())
/** fun md5(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray
* Localhost 解析
*/ inline fun md5(str: String): ByteArray
expect fun localIpAddress(): String
fun localIpAddress(): String
/**
* Ktor HttpClient. 不同平台使用不同引擎.
*/
@MiraiInternalAPI
val Http: HttpClient
}
/**
* Ktor HttpClient. 不同平台使用不同引擎.
*/
expect val Http: HttpClient
@Suppress("DuplicatedCode") // false positive. `this` is not the same for `List<Byte>` and `ByteArray` @Suppress("DuplicatedCode") // false positive. `this` is not the same for `List<Byte>` and `ByteArray`
internal fun ByteArray.checkOffsetAndLength(offset: Int, length: Int) { internal fun ByteArray.checkOffsetAndLength(offset: Int, length: Int) {

View File

@ -11,8 +11,8 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.io.core.Input import kotlinx.io.core.Input
import kotlinx.io.core.buildPacket import kotlinx.io.core.buildPacket
@ -60,6 +60,7 @@ suspend inline fun BufferedImage.suspendToExternalImage(): ExternalImage = withC
/** /**
* 读取文件头识别图片属性, 然后构造 [ExternalImage] * 读取文件头识别图片属性, 然后构造 [ExternalImage]
*/ */
@OptIn(MiraiInternalAPI::class)
@Throws(IOException::class) @Throws(IOException::class)
fun File.toExternalImage(): ExternalImage { fun File.toExternalImage(): ExternalImage {
val input = ImageIO.createImageInputStream(this) val input = ImageIO.createImageInputStream(this)
@ -71,7 +72,7 @@ fun File.toExternalImage(): ExternalImage {
return ExternalImage( return ExternalImage(
width = image.getWidth(0), width = image.getWidth(0),
height = image.getHeight(0), height = image.getHeight(0),
md5 = this.inputStream().md5(), // dont change md5 = MiraiPlatformUtils.md5(this.inputStream()), // dont change
imageFormat = image.formatName, imageFormat = image.formatName,
input = this.inputStream(), input = this.inputStream(),
filename = this.name filename = this.name

View File

@ -7,78 +7,103 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("EXPERIMENTAL_API_USAGE") @file:Suppress("EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE")
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO import io.ktor.client.engine.cio.CIO
import io.ktor.util.KtorExperimentalAPI
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.io.ByteArrayPool import net.mamoe.mirai.utils.io.ByteArrayPool
import java.io.* import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.net.InetAddress import java.net.InetAddress
import java.security.MessageDigest import java.security.MessageDigest
import java.util.zip.Deflater
import java.util.zip.Inflater import java.util.zip.Inflater
actual fun md5(byteArray: ByteArray, offset: Int, length: Int): ByteArray =
MessageDigest.getInstance("MD5").apply { update(byteArray, offset, length) }.digest()
fun InputStream.md5(): ByteArray = this.use {
val digest = MessageDigest.getInstance("md5")
digest.reset()
this.use { input ->
object : OutputStream() {
override fun write(b: Int) {
digest.update(b.toByte())
}
}.use { output ->
input.copyTo(output)
}
}
return digest.digest()
}
fun DataInput.md5(): ByteArray {
val digest = MessageDigest.getInstance("md5")
digest.reset()
val buffer = byteArrayOf(1)
while (true) {
try {
this.readFully(buffer)
} catch (e: EOFException) {
break
}
digest.update(buffer[0])
}
return digest.digest()
}
actual fun localIpAddress(): String = InetAddress.getLocalHost().hostAddress
actual val Http: HttpClient get() = HttpClient(CIO)
@OptIn(MiraiInternalAPI::class)
actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray {
this.checkOffsetAndLength(offset, length)
if (length == 0) return ByteArray(0)
val inflater = Inflater()
inflater.reset()
ByteArrayOutputStream().use { output ->
inflater.setInput(this, offset, length)
ByteArrayPool.useInstance {
while (!inflater.finished()) {
output.write(it, 0, inflater.inflate(it))
}
}
inflater.end()
return output.toByteArray()
}
}
/** /**
* 时间戳 * 时间戳
*/ */
actual val currentTimeMillis: Long actual val currentTimeMillis: Long
get() = System.currentTimeMillis() get() = System.currentTimeMillis()
@MiraiInternalAPI
actual object MiraiPlatformUtils {
actual fun unzip(data: ByteArray, offset: Int, length: Int): ByteArray {
data.checkOffsetAndLength(offset, length)
if (length == 0) return ByteArray(0)
val inflater = Inflater()
inflater.reset()
ByteArrayOutputStream().use { output ->
inflater.setInput(data, offset, length)
ByteArrayPool.useInstance {
while (!inflater.finished()) {
output.write(it, 0, inflater.inflate(it))
}
}
inflater.end()
return output.toByteArray()
}
}
actual fun zip(data: ByteArray, offset: Int, length: Int): ByteArray {
data.checkOffsetAndLength(offset, length)
if (length == 0) return ByteArray(0)
val inflater = Deflater()
inflater.reset()
ByteArrayOutputStream().use { output ->
inflater.setInput(data, offset, length)
ByteArrayPool.useInstance {
while (!inflater.finished()) {
output.write(it, 0, inflater.deflate(it))
}
}
inflater.end()
return output.toByteArray()
}
}
actual fun md5(data: ByteArray, offset: Int, length: Int): ByteArray {
data.checkOffsetAndLength(offset, length)
return MessageDigest.getInstance("MD5").apply { update(data, offset, length) }.digest()
}
actual inline fun md5(str: String): ByteArray = md5(str.toByteArray())
/**
* Ktor HttpClient. 不同平台使用不同引擎.
*/
@OptIn(KtorExperimentalAPI::class)
actual val Http: HttpClient
get() = HttpClient(CIO)
/**
* Localhost 解析
*/
actual fun localIpAddress(): String = runCatching {
InetAddress.getLocalHost().hostAddress
}.getOrElse { "192.168.1.123" }
fun md5(stream: InputStream): ByteArray {
val digest = MessageDigest.getInstance("md5")
digest.reset()
stream.use { input ->
object : OutputStream() {
override fun write(b: Int) {
digest.update(b.toByte())
}
}.use { output ->
input.copyTo(output)
}
}
return digest.digest()
}
}

View File

@ -15,6 +15,8 @@ import kotlinx.serialization.Transient
import kotlinx.serialization.UnstableDefault import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration import kotlinx.serialization.json.JsonConfiguration
import net.mamoe.mirai.utils.MiraiPlatformUtils.localIpAddress
import net.mamoe.mirai.utils.MiraiPlatformUtils.md5
import net.mamoe.mirai.utils.io.getRandomByteArray import net.mamoe.mirai.utils.io.getRandomByteArray
import net.mamoe.mirai.utils.io.getRandomString import net.mamoe.mirai.utils.io.getRandomString
import java.io.File import java.io.File
@ -37,7 +39,7 @@ fun File.loadAsDeviceInfo(context: Context = ContextImpl()): DeviceInfo {
private val JSON = Json(JsonConfiguration.Stable) private val JSON = Json(JsonConfiguration.Stable)
@Serializable @Serializable
@OptIn(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
actual open class SystemDeviceInfo actual constructor() : DeviceInfo() { actual open class SystemDeviceInfo actual constructor() : DeviceInfo() {
actual constructor(context: Context) : this() { actual constructor(context: Context) : this() {
this.context = context this.context = context

View File

@ -9,7 +9,8 @@
package net.mamoe.mirai.utils.cryptor package net.mamoe.mirai.utils.cryptor
import net.mamoe.mirai.utils.md5 import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiPlatformUtils
import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.* import java.security.*
import java.security.spec.ECGenParameterSpec import java.security.spec.ECGenParameterSpec
@ -58,6 +59,7 @@ actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
.genKeyPair()) .genKeyPair())
} }
@OptIn(MiraiInternalAPI::class)
actual fun calculateShareKey( actual fun calculateShareKey(
privateKey: ECDHPrivateKey, privateKey: ECDHPrivateKey,
publicKey: ECDHPublicKey publicKey: ECDHPublicKey
@ -65,7 +67,7 @@ actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
val instance = KeyAgreement.getInstance("ECDH", "BC") val instance = KeyAgreement.getInstance("ECDH", "BC")
instance.init(privateKey) instance.init(privateKey)
instance.doPhase(publicKey, true) instance.doPhase(publicKey, true)
return md5(instance.generateSecret()) return MiraiPlatformUtils.md5(instance.generateSecret())
} }
actual fun constructPublicKey(key: ByteArray): ECDHPublicKey { actual fun constructPublicKey(key: ByteArray): ECDHPublicKey {