Commit f2608d69 authored by Him188's avatar Him188

Merge remote-tracking branch 'origin/master'

parents a8a0d583 454057f5
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
开发版本. 频繁更新, 不保证高稳定性 开发版本. 频繁更新, 不保证高稳定性
## `0.27.0` 2020/3/8
- 支持 `XML`, `Json`, `LightApp``RichMessage`
## `0.26.2` 2020/3/8 ## `0.26.2` 2020/3/8
- 新增 `MessageChain.repeat``MessageChain.times` - 新增 `MessageChain.repeat``MessageChain.times`
- JVM 平台下 `PlatformLogger` 可重定向输出 - JVM 平台下 `PlatformLogger` 可重定向输出
......
# style guide # style guide
kotlin.code.style=official kotlin.code.style=official
# config # config
miraiVersion=0.26.2 miraiVersion=0.27.0
kotlin.incremental.multiplatform=true kotlin.incremental.multiplatform=true
kotlin.parallel.tasks.in.project=true kotlin.parallel.tasks.in.project=true
# kotlin # kotlin
......
...@@ -78,6 +78,7 @@ internal class QQImpl( ...@@ -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( ...@@ -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,
......
...@@ -222,7 +222,7 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -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()
} }
} }
......
...@@ -9,18 +9,14 @@ ...@@ -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( ...@@ -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,25 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB ...@@ -243,6 +239,25 @@ 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 LightApp -> elements.add(
ImMsgBody.Elem(
lightApp = ImMsgBody.LightAppElem(
data = byteArrayOf(1) + MiraiPlatformUtils.zip(it.content.toByteArray())
)
)
)
is RichMessage -> elements.add(
ImMsgBody.Elem(
richMsg = ImMsgBody.RichMsg(
serviceId = when (it) {
is XmlMessage -> 60
is JsonMessage -> 1
else -> error("unsupported RichMessage")
},
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))
...@@ -269,9 +284,10 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB ...@@ -269,9 +284,10 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB
} }
this.forEach(::transformOneMessage) this.forEach(::transformOneMessage)
// if(this.any<QuoteReply>()){ if (this.any<RichMessage>()) {
elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes()))) // 08 09 78 00 A0 01 81 DC 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00
// } elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "08 09 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00".hexToBytes())))
} else elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes())))
return elements return elements
} }
...@@ -400,6 +416,23 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChainBuilde ...@@ -400,6 +416,23 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChainBuilde
} }
} }
} }
it.lightApp != null -> {
val content = MiraiPlatformUtils.unzip(it.lightApp.data, 1).encodeToString()
message.add(LightApp(content))
}
it.richMsg != null -> {
val content = MiraiPlatformUtils.unzip(it.richMsg.template1, 1).encodeToString()
when (it.richMsg.serviceId) {
1 -> message.add(JsonMessage(content))
60 -> message.add(XmlMessage(content))
else -> {
@Suppress("DEPRECATION")
MiraiLogger.debug {
"unknown richMsg.serviceId: ${it.richMsg.serviceId}, content=${it.richMsg.template1.contentToString()}, \ntryUnzip=${content}"
}
}
}
}
} }
} }
......
...@@ -191,8 +191,9 @@ internal open class QQAndroidClient( ...@@ -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(
......
...@@ -26,6 +26,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY ...@@ -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 ...@@ -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
......
...@@ -342,7 +342,7 @@ internal object KnownPacketFactories { ...@@ -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)
......
...@@ -15,9 +15,10 @@ import kotlinx.io.core.toByteArray ...@@ -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( ...@@ -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( ...@@ -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( ...@@ -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( ...@@ -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
} }
......
...@@ -23,9 +23,10 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory ...@@ -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 { ...@@ -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 { ...@@ -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",
......
...@@ -84,7 +84,7 @@ internal class WtLogin { ...@@ -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))
} }
} }
} }
......
...@@ -44,6 +44,7 @@ fun Bitmap.toExternalImage(formatName: String = "gif"): ExternalImage { ...@@ -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 { ...@@ -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(),
......
...@@ -21,6 +21,8 @@ import kotlinx.serialization.Transient ...@@ -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() { ...@@ -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() { ...@@ -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()
......
...@@ -10,7 +10,8 @@ ...@@ -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) { ...@@ -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
......
...@@ -7,6 +7,8 @@ ...@@ -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,87 @@ import io.ktor.util.KtorExperimentalAPI ...@@ -15,88 +17,87 @@ 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
/** /**
* 时间戳
*/
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 deflater = Deflater()
deflater.setInput(data, offset, length)
deflater.finish()
ByteArrayPool.useInstance {
return it.take(deflater.deflate(it)).toByteArray().also { deflater.end() }
}
}
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. 不同平台使用不同引擎. * Ktor HttpClient. 不同平台使用不同引擎.
*/ */
@OptIn(KtorExperimentalAPI::class) @OptIn(KtorExperimentalAPI::class)
actual val Http: HttpClient actual val Http: HttpClient
get() = HttpClient(CIO) get() = HttpClient(CIO)
/** /**
* Localhost 解析 * Localhost 解析
*/ */
actual fun localIpAddress(): String = runCatching { actual fun localIpAddress(): String = runCatching {
InetAddress.getLocalHost().hostAddress InetAddress.getLocalHost().hostAddress
}.getOrElse { "192.168.1.123" } }.getOrElse { "192.168.1.123" }
/** fun md5(stream: InputStream): ByteArray {
* 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") val digest = MessageDigest.getInstance("md5")
digest.reset() digest.reset()
this.readInSequence { stream.readInSequence {
digest.update(it.toByte()) digest.update(it.toByte())
} }
return digest.digest() 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) {
private inline fun InputStream.readInSequence(block: (Int) -> Unit) {
var read: Int var read: Int
while (this.read().also { read = it } != -1) { while (this.read().also { read = it } != -1) {
block(read) 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()
} }
} }
\ No newline at end of file
/**
* 时间戳
*/
actual val currentTimeMillis: Long get() = System.currentTimeMillis()
\ No newline at end of file
...@@ -14,7 +14,7 @@ package net.mamoe.mirai ...@@ -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( ...@@ -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
......
/*
* 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.message.data
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.SinceMirai
/**
* Json 消息.
*
* @see LightApp 一些消息实际上是 [LightApp]
*/
@SinceMirai("0.27.0")
@OptIn(MiraiExperimentalAPI::class)
class JsonMessage(override val content: String) : RichMessage {
companion object Key : Message.Key<JsonMessage>
// serviceId = 1
override fun toString(): String = content
}
\ No newline at end of file
/*
* 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.message.data
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.SinceMirai
/**
* 小程序分享, 如音乐分享
*/
@OptIn(MiraiExperimentalAPI::class)
@SinceMirai("0.27.0")
class LightApp constructor(override val content: String) : RichMessage {
companion object Key : Message.Key<LightApp>
override fun toString(): String = content
}
\ No newline at end of file
...@@ -61,7 +61,7 @@ interface MessageChain : Message, Iterable<SingleMessage> { ...@@ -61,7 +61,7 @@ interface MessageChain : Message, Iterable<SingleMessage> {
fun <M : Message> getOrNull(key: Message.Key<M>): M? = firstOrNull(key) fun <M : Message> getOrNull(key: Message.Key<M>): M? = firstOrNull(key)
/** /**
* 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face], [XMLMessage], [QuoteReply]. * 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face], [XmlMessage], [QuoteReply].
* 仅供 `Java` 使用 * 仅供 `Java` 使用
*/ */
@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME") @Suppress("FunctionName", "INAPPLICABLE_JVM_NAME")
...@@ -73,7 +73,7 @@ interface MessageChain : Message, Iterable<SingleMessage> { ...@@ -73,7 +73,7 @@ interface MessageChain : Message, Iterable<SingleMessage> {
} }
/** /**
* 遍历每一个消息, 即 [MessageSource] [At], [AtAll], [PlainText], [Image], [Face], [XMLMessage], [QuoteReply]. * 遍历每一个消息, 即 [MessageSource] [At], [AtAll], [PlainText], [Image], [Face], [XmlMessage], [QuoteReply].
* 仅供 `Java` 使用 * 仅供 `Java` 使用
*/ */
@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME") @Suppress("FunctionName", "INAPPLICABLE_JVM_NAME")
...@@ -88,7 +88,7 @@ interface MessageChain : Message, Iterable<SingleMessage> { ...@@ -88,7 +88,7 @@ interface MessageChain : Message, Iterable<SingleMessage> {
// region accessors // region accessors
/** /**
* 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face], [XMLMessage], [QuoteReply] * 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face], [XmlMessage], [QuoteReply]
*/ */
@JvmSynthetic @JvmSynthetic
inline fun MessageChain.foreachContent(block: (Message) -> Unit) { inline fun MessageChain.foreachContent(block: (Message) -> Unit) {
...@@ -130,6 +130,10 @@ fun <M : Message> MessageChain.firstOrNull(key: Message.Key<M>): M? = when (key) ...@@ -130,6 +130,10 @@ fun <M : Message> MessageChain.firstOrNull(key: Message.Key<M>): M? = when (key)
Face -> first<Face>() Face -> first<Face>()
QuoteReply -> first<QuoteReply>() QuoteReply -> first<QuoteReply>()
MessageSource -> first<MessageSource>() MessageSource -> first<MessageSource>()
XmlMessage -> first<XmlMessage>()
JsonMessage -> first<JsonMessage>()
RichMessage -> first<RichMessage>()
LightApp -> first<LightApp>()
else -> null else -> null
} as M? } as M?
......
...@@ -7,20 +7,20 @@ ...@@ -7,20 +7,20 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
package net.mamoe.mirai.data package net.mamoe.mirai.message.data
import io.ktor.client.request.get import net.mamoe.mirai.utils.SinceMirai
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.utils.Http
interface ImageLink { /**
/** * XML 消息等富文本消息
* 原图 *
* @see XmlMessage
* @see JsonMessage
* @see LightApp
*/ */
val original: String @SinceMirai("0.27.0")
interface RichMessage : MessageContent {
suspend fun downloadAsByteArray(): ByteArray = download().readBytes() companion object Key : Message.Key<RichMessage>
suspend fun download(): ByteReadPacket = Http.get(original) val content: String
} }
\ No newline at end of file
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.SinceMirai
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
...@@ -23,20 +24,25 @@ import kotlin.jvm.JvmName ...@@ -23,20 +24,25 @@ import kotlin.jvm.JvmName
* *
* @see buildXMLMessage * @see buildXMLMessage
*/ */
@MiraiExperimentalAPI @SinceMirai("0.27.0")
inline class XMLMessage(val stringValue: String) : Message, MessageContent { @OptIn(MiraiExperimentalAPI::class)
override fun followedBy(tail: Message): Nothing = error("XMLMessage Message cannot be followed") class XmlMessage constructor(override val content: String) : RichMessage {
override fun toString(): String = stringValue companion object Key : Message.Key<XmlMessage>
// override val serviceId: Int get() = 60
override fun toString(): String = content
} }
/** /**
* 构造一条 XML 消息 * 构造一条 XML 消息
*/ */
@MiraiExperimentalAPI("还未支持") @SinceMirai("0.27.0")
inline fun buildXMLMessage(block: @XMLDsl XMLMessageBuilder.() -> Unit): XMLMessage = @MiraiExperimentalAPI
XMLMessage(XMLMessageBuilder().apply(block).text) inline fun buildXMLMessage(block: @XMLDsl XMLMessageBuilder.() -> Unit): XmlMessage =
XmlMessage(XMLMessageBuilder().apply(block).text)
@Suppress("NOTHING_TO_INLINE") @SinceMirai("0.27.0")
@XMLDsl @XMLDsl
class ItemBuilder( class ItemBuilder(
var bg: Int = 0, var bg: Int = 0,
...@@ -46,21 +52,20 @@ class ItemBuilder( ...@@ -46,21 +52,20 @@ class ItemBuilder(
internal val builder: StringBuilder = StringBuilder() internal val builder: StringBuilder = StringBuilder()
val text: String get() = "<item bg='$bg' layout='$layout'>$builder</item>" val text: String get() = "<item bg='$bg' layout='$layout'>$builder</item>"
inline fun summary(text: String, color: String = "#FFFFFF") { fun summary(text: String, color: String = "#000000") {
this.builder.append("<summary color='$color'>$text</summary>") this.builder.append("<summary color='$color'>$text</summary>")
} }
inline fun title(text: String, size: Int = 18, color: String = "#FFFFFF") { fun title(text: String, size: Int = 25, color: String = "#000000") {
this.builder.append("<title size='$size' color='$color'>$text</title>") this.builder.append("<title size='$size' color='$color'>$text</title>")
} }
inline fun picture(coverUrl: String) { fun picture(coverUrl: String) {
this.builder.append("<picture cover='$coverUrl'/>") this.builder.append("<picture cover='$coverUrl'/>")
} }
} }
@XMLDsl @XMLDsl
@Suppress("NOTHING_TO_INLINE")
class XMLMessageBuilder( class XMLMessageBuilder(
var templateId: Int = 1, var templateId: Int = 1,
var serviceId: Int = 1, var serviceId: Int = 1,
...@@ -70,7 +75,7 @@ class XMLMessageBuilder( ...@@ -70,7 +75,7 @@ class XMLMessageBuilder(
*/ */
var actionData: String = "", var actionData: String = "",
/** /**
* 摘要 * 摘要, 在官方客户端内消息列表中显示
*/ */
var brief: String = "", var brief: String = "",
var flag: Int = 3, var flag: Int = 3,
...@@ -89,11 +94,11 @@ class XMLMessageBuilder( ...@@ -89,11 +94,11 @@ class XMLMessageBuilder(
"</msg>" "</msg>"
@XMLDsl @XMLDsl
inline fun item(block: @XMLDsl ItemBuilder.() -> Unit) { fun item(block: @XMLDsl ItemBuilder.() -> Unit) {
builder.append(ItemBuilder().apply(block).text) builder.append(ItemBuilder().apply(block).text)
} }
inline fun source(name: String, iconURL: String = "") { fun source(name: String, iconURL: String = "") {
sourceName = name sourceName = name
sourceIconURL = iconURL sourceIconURL = iconURL
} }
...@@ -101,4 +106,4 @@ class XMLMessageBuilder( ...@@ -101,4 +106,4 @@ class XMLMessageBuilder(
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPE) @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@DslMarker @DslMarker
internal annotation class XMLDsl annotation class XMLDsl
\ No newline at end of file \ No newline at end of file
...@@ -94,6 +94,7 @@ abstract class DeviceInfo { ...@@ -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( ...@@ -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( ...@@ -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(
......
...@@ -17,7 +17,7 @@ import kotlin.annotation.AnnotationTarget.* ...@@ -17,7 +17,7 @@ import kotlin.annotation.AnnotationTarget.*
* 这些 API 可能会在任意时刻更改, 且不会发布任何预警. * 这些 API 可能会在任意时刻更改, 且不会发布任何预警.
* 非常不建议在发行版本中使用这些 API. * 非常不建议在发行版本中使用这些 API.
*/ */
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.SOURCE)
@RequiresOptIn(level = RequiresOptIn.Level.ERROR) @RequiresOptIn(level = RequiresOptIn.Level.ERROR)
@Target( @Target(
CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR, CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR,
...@@ -35,7 +35,7 @@ annotation class MiraiInternalAPI( ...@@ -35,7 +35,7 @@ annotation class MiraiInternalAPI(
* 这些 API 不具有稳定性, 且可能会在任意时刻更改. * 这些 API 不具有稳定性, 且可能会在任意时刻更改.
* 不建议在发行版本中使用这些 API. * 不建议在发行版本中使用这些 API.
*/ */
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.SOURCE)
@RequiresOptIn(level = RequiresOptIn.Level.WARNING) @RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
annotation class MiraiExperimentalAPI( annotation class MiraiExperimentalAPI(
...@@ -48,7 +48,7 @@ annotation class MiraiExperimentalAPI( ...@@ -48,7 +48,7 @@ annotation class MiraiExperimentalAPI(
* 这些 API 不具有稳定性, 可能会在任意时刻更改, 并且效率非常低下. * 这些 API 不具有稳定性, 可能会在任意时刻更改, 并且效率非常低下.
* 非常不建议在发行版本中使用这些 API. * 非常不建议在发行版本中使用这些 API.
*/ */
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.SOURCE)
@RequiresOptIn(level = RequiresOptIn.Level.WARNING) @RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
annotation class MiraiDebugAPI( annotation class MiraiDebugAPI(
...@@ -59,6 +59,6 @@ annotation class MiraiDebugAPI( ...@@ -59,6 +59,6 @@ annotation class MiraiDebugAPI(
* 标记一个自 Mirai 某个版本起才支持的 API. * 标记一个自 Mirai 某个版本起才支持的 API.
*/ */
@Target(CLASS, PROPERTY, FIELD, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS) @Target(CLASS, PROPERTY, FIELD, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS)
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.SOURCE)
@MustBeDocumented @MustBeDocumented
annotation class SinceMirai(val version: String) annotation class SinceMirai(val version: String)
\ No newline at end of file
...@@ -12,7 +12,6 @@ ...@@ -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 ...@@ -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 解析
*/
expect fun localIpAddress(): String
/** inline fun md5(str: String): ByteArray
fun localIpAddress(): String
/**
* Ktor HttpClient. 不同平台使用不同引擎. * Ktor HttpClient. 不同平台使用不同引擎.
*/ */
expect val Http: HttpClient @MiraiInternalAPI
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) {
......
/*
* 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.utils
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.utils.io.encodeToString
import kotlin.test.Test
import kotlin.test.assertEquals
internal class PlatformUtilsTest {
@OptIn(MiraiInternalAPI::class)
@Test
fun testZip() {
assertEquals("test", MiraiPlatformUtils.unzip(MiraiPlatformUtils.zip("test".toByteArray())).encodeToString())
}
}
\ No newline at end of file
...@@ -11,8 +11,8 @@ ...@@ -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 ...@@ -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 { ...@@ -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
......
...@@ -15,7 +15,7 @@ import java.util.* ...@@ -15,7 +15,7 @@ import java.util.*
/** /**
* JVM 控制台日志实现 * JVM 控制台日志实现
*/ */
actual open class PlatformLogger @JvmOverloads constructor( actual open class PlatformLogger constructor(
override val identity: String? = "Mirai", override val identity: String? = "Mirai",
open val output: (String) -> Unit open val output: (String) -> Unit
) : MiraiLoggerPlatformBase() { ) : MiraiLoggerPlatformBase() {
......
...@@ -7,65 +7,39 @@ ...@@ -7,65 +7,39 @@
* 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 { actual val currentTimeMillis: Long
val digest = MessageDigest.getInstance("md5") get() = System.currentTimeMillis()
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) @MiraiInternalAPI
actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray { actual object MiraiPlatformUtils {
this.checkOffsetAndLength(offset, length) actual fun unzip(data: ByteArray, offset: Int, length: Int): ByteArray {
data.checkOffsetAndLength(offset, length)
if (length == 0) return ByteArray(0) if (length == 0) return ByteArray(0)
val inflater = Inflater() val inflater = Inflater()
inflater.reset() inflater.reset()
ByteArrayOutputStream().use { output -> ByteArrayOutputStream().use { output ->
inflater.setInput(this, offset, length) inflater.setInput(data, offset, length)
ByteArrayPool.useInstance { ByteArrayPool.useInstance {
while (!inflater.finished()) { while (!inflater.finished()) {
output.write(it, 0, inflater.inflate(it)) output.write(it, 0, inflater.inflate(it))
...@@ -75,10 +49,55 @@ actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray { ...@@ -75,10 +49,55 @@ actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray {
inflater.end() inflater.end()
return output.toByteArray() return output.toByteArray()
} }
} }
/** actual fun zip(data: ByteArray, offset: Int, length: Int): ByteArray {
* 时间戳 data.checkOffsetAndLength(offset, length)
if (length == 0) return ByteArray(0)
val deflater = Deflater()
deflater.setInput(data, offset, length)
deflater.finish()
ByteArrayPool.useInstance {
return it.take(deflater.deflate(it)).toByteArray().also { deflater.end() }
}
}
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. 不同平台使用不同引擎.
*/ */
actual val currentTimeMillis: Long @OptIn(KtorExperimentalAPI::class)
get() = System.currentTimeMillis() actual val Http: HttpClient
\ No newline at end of file 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()
}
}
\ No newline at end of file
...@@ -15,6 +15,8 @@ import kotlinx.serialization.Transient ...@@ -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 { ...@@ -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
......
...@@ -9,7 +9,8 @@ ...@@ -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) { ...@@ -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) { ...@@ -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 {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment