mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-20 17:29:32 +08:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
f2608d6956
@ -2,6 +2,9 @@
|
||||
|
||||
开发版本. 频繁更新, 不保证高稳定性
|
||||
|
||||
## `0.27.0` 2020/3/8
|
||||
- 支持 `XML`, `Json`, `LightApp` 等 `RichMessage`
|
||||
|
||||
## `0.26.2` 2020/3/8
|
||||
- 新增 `MessageChain.repeat` 与 `MessageChain.times`
|
||||
- JVM 平台下 `PlatformLogger` 可重定向输出
|
||||
|
@ -1,7 +1,7 @@
|
||||
# style guide
|
||||
kotlin.code.style=official
|
||||
# config
|
||||
miraiVersion=0.26.2
|
||||
miraiVersion=0.27.0
|
||||
kotlin.incremental.multiplatform=true
|
||||
kotlin.parallel.tasks.in.project=true
|
||||
# kotlin
|
||||
|
@ -78,6 +78,7 @@ internal class QQImpl(
|
||||
return MessageReceipt(source, this, null)
|
||||
}
|
||||
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage = try {
|
||||
if (BeforeImageUploadEvent(this, image).broadcast().isCancelled) {
|
||||
throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
|
||||
@ -111,7 +112,7 @@ internal class QQImpl(
|
||||
ImageUploadEvent.Succeed(this@QQImpl, image, it).broadcast()
|
||||
}
|
||||
is LongConn.OffPicUp.Response.RequireUpload -> {
|
||||
Http.postImage(
|
||||
MiraiPlatformUtils.Http.postImage(
|
||||
"0x6ff0070",
|
||||
bot.uin,
|
||||
null,
|
||||
|
@ -222,7 +222,7 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
}
|
||||
|
||||
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 @@
|
||||
|
||||
package net.mamoe.mirai.qqandroid.message
|
||||
|
||||
import kotlinx.io.core.buildPacket
|
||||
import kotlinx.io.core.discardExact
|
||||
import kotlinx.io.core.readBytes
|
||||
import kotlinx.io.core.readUInt
|
||||
import kotlinx.io.core.*
|
||||
import net.mamoe.mirai.LowLevelAPI
|
||||
import net.mamoe.mirai.contact.Member
|
||||
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.MsgComm
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
import net.mamoe.mirai.utils.MiraiDebugAPI
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.*
|
||||
import net.mamoe.mirai.utils.io.encodeToString
|
||||
import net.mamoe.mirai.utils.io.hexToBytes
|
||||
import net.mamoe.mirai.utils.io.read
|
||||
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> {
|
||||
val elements = mutableListOf<ImMsgBody.Elem>()
|
||||
|
||||
@ -243,6 +239,25 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB
|
||||
elements.add(ImMsgBody.Elem(text = it.toJceData()))
|
||||
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 OnlineGroupImageImpl -> elements.add(ImMsgBody.Elem(customFace = it.delegate))
|
||||
is OnlineFriendImageImpl -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate))
|
||||
@ -269,9 +284,10 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB
|
||||
}
|
||||
this.forEach(::transformOneMessage)
|
||||
|
||||
// if(this.any<QuoteReply>()){
|
||||
elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes())))
|
||||
// }
|
||||
if (this.any<RichMessage>()) {
|
||||
// 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
|
||||
}
|
||||
@ -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(
|
||||
lateinit var t104: ByteArray
|
||||
}
|
||||
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
internal fun generateTgtgtKey(guid: ByteArray): ByteArray =
|
||||
md5(getRandomByteArray(16) + guid)
|
||||
MiraiPlatformUtils.md5(getRandomByteArray(16) + guid)
|
||||
|
||||
|
||||
internal class ReserveUinInfo(
|
||||
|
@ -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.io.*
|
||||
import kotlinx.serialization.InternalSerializationApi
|
||||
import net.mamoe.mirai.utils.MiraiPlatformUtils
|
||||
|
||||
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
|
||||
internal fun createImageDataPacketSequence( // RequestDataTrans
|
||||
@ -77,7 +78,7 @@ internal fun createImageDataPacketSequence( // RequestDataTrans
|
||||
dataoffset = offset,
|
||||
filesize = dataSize.toLong(),
|
||||
serviceticket = uKey,
|
||||
md5 = net.mamoe.mirai.utils.md5(chunkedInput.buffer, 0, chunkedInput.bufferSize),
|
||||
md5 = MiraiPlatformUtils.md5(chunkedInput.buffer, 0, chunkedInput.bufferSize),
|
||||
fileMd5 = fileMd5,
|
||||
flag = 0,
|
||||
rtcode = 0
|
||||
|
@ -342,7 +342,7 @@ internal object KnownPacketFactories {
|
||||
1 -> {
|
||||
input.discardExact(4)
|
||||
input.useBytes { data, length ->
|
||||
data.unzip(length = length).let {
|
||||
MiraiPlatformUtils.unzip(data, 0, length).let {
|
||||
val size = it.toInt()
|
||||
if (size == it.size || size == it.size + 4) {
|
||||
it.toReadPacket(offset = 4)
|
||||
|
@ -15,9 +15,10 @@ import kotlinx.io.core.toByteArray
|
||||
import kotlinx.io.core.writeFully
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.LoginType
|
||||
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.io.*
|
||||
import net.mamoe.mirai.utils.md5
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
@ -76,6 +77,7 @@ fun BytePacketBuilder.t18(
|
||||
} shouldEqualsTo 22
|
||||
}
|
||||
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
fun BytePacketBuilder.t106(
|
||||
appId: Long = 16L,
|
||||
subAppId: Long = 537062845L,
|
||||
@ -96,7 +98,7 @@ fun BytePacketBuilder.t106(
|
||||
guid?.requireSize(16)
|
||||
|
||||
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
|
||||
writeInt(Random.nextInt())
|
||||
writeInt(5)//ssoVer
|
||||
@ -321,12 +323,13 @@ fun BytePacketBuilder.t144(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
fun BytePacketBuilder.t109(
|
||||
androidId: ByteArray
|
||||
) {
|
||||
writeShort(0x109)
|
||||
writeShortLVPacket {
|
||||
writeFully(md5(androidId))
|
||||
writeFully(MiraiPlatformUtils.md5(androidId))
|
||||
} shouldEqualsTo 16
|
||||
}
|
||||
|
||||
@ -556,21 +559,23 @@ fun BytePacketBuilder.t400(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
fun BytePacketBuilder.t187(
|
||||
macAddress: ByteArray
|
||||
) {
|
||||
writeShort(0x187)
|
||||
writeShortLVPacket {
|
||||
writeFully(md5(macAddress)) // may be md5
|
||||
writeFully(MiraiPlatformUtils.md5(macAddress)) // may be md5
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
fun BytePacketBuilder.t188(
|
||||
androidId: ByteArray
|
||||
) {
|
||||
writeShort(0x188)
|
||||
writeShortLVPacket {
|
||||
writeFully(md5(androidId))
|
||||
writeFully(MiraiPlatformUtils.md5(androidId))
|
||||
} shouldEqualsTo 16
|
||||
}
|
||||
|
||||
|
@ -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.writeSsoPacket
|
||||
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.toReadPacket
|
||||
import net.mamoe.mirai.utils.localIpAddress
|
||||
|
||||
@Suppress("EnumEntryName")
|
||||
internal enum class RegPushReason {
|
||||
@ -89,6 +90,7 @@ internal class StatSvc {
|
||||
|
||||
private const val subAppId = 537062845L
|
||||
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
operator fun invoke(
|
||||
client: QQAndroidClient,
|
||||
regPushReason: RegPushReason = RegPushReason.appRegister
|
||||
@ -138,7 +140,7 @@ internal class StatSvc {
|
||||
strOSVer = client.device.version.release.encodeToString(),
|
||||
|
||||
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)))
|
||||
},
|
||||
strVendorName = "MIUI",
|
||||
|
@ -84,7 +84,7 @@ internal class WtLogin {
|
||||
t8(2052)
|
||||
t104(client.t104)
|
||||
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 {
|
||||
/**
|
||||
* 读取文件头识别图片属性, 然后构造 [ExternalImage]
|
||||
*/
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
@Throws(IOException::class)
|
||||
fun File.toExternalImage(): ExternalImage {
|
||||
val input = BitmapFactory.decodeFile(this.absolutePath)
|
||||
@ -52,7 +53,7 @@ fun File.toExternalImage(): ExternalImage {
|
||||
return ExternalImage(
|
||||
width = input.width,
|
||||
height = input.height,
|
||||
md5 = this.inputStream().use { it.md5() },
|
||||
md5 = this.inputStream().use { MiraiPlatformUtils.md5(it) },
|
||||
imageFormat = this.nameWithoutExtension,
|
||||
input = this.inputStream().asInput(IoBuffer.Pool),
|
||||
inputSize = this.length(),
|
||||
|
@ -21,6 +21,8 @@ import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.UnstableDefault
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonConfiguration
|
||||
import net.mamoe.mirai.utils.MiraiPlatformUtils.localIpAddress
|
||||
import net.mamoe.mirai.utils.MiraiPlatformUtils.md5
|
||||
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()
|
||||
}.getOrElse { byteArrayOf() }
|
||||
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
override val imsiMd5: ByteArray
|
||||
@SuppressLint("HardwareIds")
|
||||
get() = md5(kotlin.runCatching {
|
||||
@ -117,6 +120,8 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() {
|
||||
(context.applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).deviceId
|
||||
}
|
||||
}.getOrElse { "" }
|
||||
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
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 apn: ByteArray get() = "wifi".toByteArray()
|
||||
|
@ -10,7 +10,8 @@
|
||||
package net.mamoe.mirai.utils.cryptor
|
||||
|
||||
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.spec.ECGenParameterSpec
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
@ -71,6 +72,7 @@ actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
|
||||
.genKeyPair())
|
||||
}
|
||||
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
actual fun calculateShareKey(
|
||||
privateKey: ECDHPrivateKey,
|
||||
publicKey: ECDHPublicKey
|
||||
|
@ -7,6 +7,8 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
@ -15,88 +17,87 @@ import io.ktor.util.KtorExperimentalAPI
|
||||
import kotlinx.io.pool.useInstance
|
||||
import net.mamoe.mirai.utils.io.ByteArrayPool
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.DataInput
|
||||
import java.io.EOFException
|
||||
import java.io.InputStream
|
||||
import java.net.InetAddress
|
||||
import java.security.MessageDigest
|
||||
import java.util.zip.Deflater
|
||||
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 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. 不同平台使用不同引擎.
|
||||
*/
|
||||
@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)
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ package net.mamoe.mirai
|
||||
import kotlinx.io.core.toByteArray
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.md5
|
||||
import net.mamoe.mirai.utils.MiraiPlatformUtils
|
||||
import kotlin.annotation.AnnotationTarget.*
|
||||
|
||||
@MiraiInternalAPI
|
||||
@ -28,7 +28,7 @@ data class BotAccount(
|
||||
@MiraiInternalAPI
|
||||
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 {
|
||||
if (this === other) return true
|
||||
|
@ -1,26 +0,0 @@
|
||||
/*
|
||||
* 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.data
|
||||
|
||||
import io.ktor.client.request.get
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.readBytes
|
||||
import net.mamoe.mirai.utils.Http
|
||||
|
||||
interface ImageLink {
|
||||
/**
|
||||
* 原图
|
||||
*/
|
||||
val original: String
|
||||
|
||||
suspend fun downloadAsByteArray(): ByteArray = download().readBytes()
|
||||
|
||||
suspend fun download(): ByteReadPacket = Http.get(original)
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
@ -61,7 +61,7 @@ interface MessageChain : Message, Iterable<SingleMessage> {
|
||||
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` 使用
|
||||
*/
|
||||
@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME")
|
||||
@ -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` 使用
|
||||
*/
|
||||
@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME")
|
||||
@ -88,7 +88,7 @@ interface MessageChain : Message, Iterable<SingleMessage> {
|
||||
// region accessors
|
||||
|
||||
/**
|
||||
* 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face], [XMLMessage], [QuoteReply]
|
||||
* 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face], [XmlMessage], [QuoteReply]
|
||||
*/
|
||||
@JvmSynthetic
|
||||
inline fun MessageChain.foreachContent(block: (Message) -> Unit) {
|
||||
@ -130,6 +130,10 @@ fun <M : Message> MessageChain.firstOrNull(key: Message.Key<M>): M? = when (key)
|
||||
Face -> first<Face>()
|
||||
QuoteReply -> first<QuoteReply>()
|
||||
MessageSource -> first<MessageSource>()
|
||||
XmlMessage -> first<XmlMessage>()
|
||||
JsonMessage -> first<JsonMessage>()
|
||||
RichMessage -> first<RichMessage>()
|
||||
LightApp -> first<LightApp>()
|
||||
else -> null
|
||||
} as M?
|
||||
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.SinceMirai
|
||||
|
||||
/**
|
||||
* XML 消息等富文本消息
|
||||
*
|
||||
* @see XmlMessage
|
||||
* @see JsonMessage
|
||||
* @see LightApp
|
||||
*/
|
||||
@SinceMirai("0.27.0")
|
||||
interface RichMessage : MessageContent {
|
||||
companion object Key : Message.Key<RichMessage>
|
||||
|
||||
val content: String
|
||||
}
|
@ -15,6 +15,7 @@
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
@ -23,20 +24,25 @@ import kotlin.jvm.JvmName
|
||||
*
|
||||
* @see buildXMLMessage
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
inline class XMLMessage(val stringValue: String) : Message, MessageContent {
|
||||
override fun followedBy(tail: Message): Nothing = error("XMLMessage Message cannot be followed")
|
||||
override fun toString(): String = stringValue
|
||||
@SinceMirai("0.27.0")
|
||||
@OptIn(MiraiExperimentalAPI::class)
|
||||
class XmlMessage constructor(override val content: String) : RichMessage {
|
||||
companion object Key : Message.Key<XmlMessage>
|
||||
|
||||
// override val serviceId: Int get() = 60
|
||||
|
||||
override fun toString(): String = content
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造一条 XML 消息
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
inline fun buildXMLMessage(block: @XMLDsl XMLMessageBuilder.() -> Unit): XMLMessage =
|
||||
XMLMessage(XMLMessageBuilder().apply(block).text)
|
||||
@SinceMirai("0.27.0")
|
||||
@MiraiExperimentalAPI
|
||||
inline fun buildXMLMessage(block: @XMLDsl XMLMessageBuilder.() -> Unit): XmlMessage =
|
||||
XmlMessage(XMLMessageBuilder().apply(block).text)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
@SinceMirai("0.27.0")
|
||||
@XMLDsl
|
||||
class ItemBuilder(
|
||||
var bg: Int = 0,
|
||||
@ -46,21 +52,20 @@ class ItemBuilder(
|
||||
internal val builder: StringBuilder = StringBuilder()
|
||||
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>")
|
||||
}
|
||||
|
||||
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>")
|
||||
}
|
||||
|
||||
inline fun picture(coverUrl: String) {
|
||||
fun picture(coverUrl: String) {
|
||||
this.builder.append("<picture cover='$coverUrl'/>")
|
||||
}
|
||||
}
|
||||
|
||||
@XMLDsl
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
class XMLMessageBuilder(
|
||||
var templateId: Int = 1,
|
||||
var serviceId: Int = 1,
|
||||
@ -70,7 +75,7 @@ class XMLMessageBuilder(
|
||||
*/
|
||||
var actionData: String = "",
|
||||
/**
|
||||
* 摘要
|
||||
* 摘要, 在官方客户端内消息列表中显示
|
||||
*/
|
||||
var brief: String = "",
|
||||
var flag: Int = 3,
|
||||
@ -89,11 +94,11 @@ class XMLMessageBuilder(
|
||||
"</msg>"
|
||||
|
||||
@XMLDsl
|
||||
inline fun item(block: @XMLDsl ItemBuilder.() -> Unit) {
|
||||
fun item(block: @XMLDsl ItemBuilder.() -> Unit) {
|
||||
builder.append(ItemBuilder().apply(block).text)
|
||||
}
|
||||
|
||||
inline fun source(name: String, iconURL: String = "") {
|
||||
fun source(name: String, iconURL: String = "") {
|
||||
sourceName = name
|
||||
sourceIconURL = iconURL
|
||||
}
|
||||
@ -101,4 +106,4 @@ class XMLMessageBuilder(
|
||||
|
||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
|
||||
@DslMarker
|
||||
internal annotation class XMLDsl
|
||||
annotation class XMLDsl
|
@ -94,6 +94,7 @@ abstract class DeviceInfo {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
@Serializable
|
||||
class DeviceInfoData(
|
||||
override val display: ByteArray,
|
||||
@ -122,7 +123,8 @@ class DeviceInfoData(
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
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()
|
||||
override val androidId: ByteArray get() = display
|
||||
|
||||
@ -138,7 +140,9 @@ class DeviceInfoData(
|
||||
/**
|
||||
* 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(
|
||||
|
@ -17,7 +17,7 @@ import kotlin.annotation.AnnotationTarget.*
|
||||
* 这些 API 可能会在任意时刻更改, 且不会发布任何预警.
|
||||
* 非常不建议在发行版本中使用这些 API.
|
||||
*/
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
|
||||
@Target(
|
||||
CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR,
|
||||
@ -35,7 +35,7 @@ annotation class MiraiInternalAPI(
|
||||
* 这些 API 不具有稳定性, 且可能会在任意时刻更改.
|
||||
* 不建议在发行版本中使用这些 API.
|
||||
*/
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
|
||||
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
|
||||
annotation class MiraiExperimentalAPI(
|
||||
@ -48,7 +48,7 @@ annotation class MiraiExperimentalAPI(
|
||||
* 这些 API 不具有稳定性, 可能会在任意时刻更改, 并且效率非常低下.
|
||||
* 非常不建议在发行版本中使用这些 API.
|
||||
*/
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
|
||||
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
|
||||
annotation class MiraiDebugAPI(
|
||||
@ -59,6 +59,6 @@ annotation class MiraiDebugAPI(
|
||||
* 标记一个自 Mirai 某个版本起才支持的 API.
|
||||
*/
|
||||
@Target(CLASS, PROPERTY, FIELD, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@MustBeDocumented
|
||||
annotation class SinceMirai(val version: String)
|
@ -12,7 +12,6 @@
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
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
|
||||
|
||||
|
||||
/**
|
||||
* 解 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
|
||||
|
||||
/**
|
||||
* MD5 算法
|
||||
*
|
||||
* @return 16 bytes
|
||||
*/
|
||||
expect fun md5(byteArray: ByteArray, offset: Int = 0, length: Int = byteArray.size - offset): ByteArray
|
||||
fun zip(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray
|
||||
|
||||
inline fun md5(str: String): ByteArray = md5(str.toByteArray())
|
||||
|
||||
/**
|
||||
* Localhost 解析
|
||||
*/
|
||||
expect fun localIpAddress(): String
|
||||
fun md5(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray
|
||||
|
||||
inline fun md5(str: String): ByteArray
|
||||
|
||||
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`
|
||||
internal fun ByteArray.checkOffsetAndLength(offset: Int, length: Int) {
|
||||
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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())
|
||||
}
|
||||
}
|
@ -11,8 +11,8 @@
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import kotlinx.coroutines.io.ByteReadChannel
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.io.ByteReadChannel
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.io.core.Input
|
||||
import kotlinx.io.core.buildPacket
|
||||
@ -60,6 +60,7 @@ suspend inline fun BufferedImage.suspendToExternalImage(): ExternalImage = withC
|
||||
/**
|
||||
* 读取文件头识别图片属性, 然后构造 [ExternalImage]
|
||||
*/
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
@Throws(IOException::class)
|
||||
fun File.toExternalImage(): ExternalImage {
|
||||
val input = ImageIO.createImageInputStream(this)
|
||||
@ -71,7 +72,7 @@ fun File.toExternalImage(): ExternalImage {
|
||||
return ExternalImage(
|
||||
width = image.getWidth(0),
|
||||
height = image.getHeight(0),
|
||||
md5 = this.inputStream().md5(), // dont change
|
||||
md5 = MiraiPlatformUtils.md5(this.inputStream()), // dont change
|
||||
imageFormat = image.formatName,
|
||||
input = this.inputStream(),
|
||||
filename = this.name
|
||||
|
@ -15,7 +15,7 @@ import java.util.*
|
||||
/**
|
||||
* JVM 控制台日志实现
|
||||
*/
|
||||
actual open class PlatformLogger @JvmOverloads constructor(
|
||||
actual open class PlatformLogger constructor(
|
||||
override val identity: String? = "Mirai",
|
||||
open val output: (String) -> Unit
|
||||
) : MiraiLoggerPlatformBase() {
|
||||
|
@ -7,78 +7,97 @@
|
||||
* 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
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.cio.CIO
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import kotlinx.io.pool.useInstance
|
||||
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.security.MessageDigest
|
||||
import java.util.zip.Deflater
|
||||
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
|
||||
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 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. 不同平台使用不同引擎.
|
||||
*/
|
||||
@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()
|
||||
}
|
||||
|
||||
}
|
@ -15,6 +15,8 @@ import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.UnstableDefault
|
||||
import kotlinx.serialization.json.Json
|
||||
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.getRandomString
|
||||
import java.io.File
|
||||
@ -37,7 +39,7 @@ fun File.loadAsDeviceInfo(context: Context = ContextImpl()): DeviceInfo {
|
||||
private val JSON = Json(JsonConfiguration.Stable)
|
||||
|
||||
@Serializable
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
@OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
|
||||
actual open class SystemDeviceInfo actual constructor() : DeviceInfo() {
|
||||
actual constructor(context: Context) : this() {
|
||||
this.context = context
|
||||
|
@ -9,7 +9,8 @@
|
||||
|
||||
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 java.security.*
|
||||
import java.security.spec.ECGenParameterSpec
|
||||
@ -58,6 +59,7 @@ actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
|
||||
.genKeyPair())
|
||||
}
|
||||
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
actual fun calculateShareKey(
|
||||
privateKey: ECDHPrivateKey,
|
||||
publicKey: ECDHPublicKey
|
||||
@ -65,7 +67,7 @@ actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
|
||||
val instance = KeyAgreement.getInstance("ECDH", "BC")
|
||||
instance.init(privateKey)
|
||||
instance.doPhase(publicKey, true)
|
||||
return md5(instance.generateSecret())
|
||||
return MiraiPlatformUtils.md5(instance.generateSecret())
|
||||
}
|
||||
|
||||
actual fun constructPublicKey(key: ByteArray): ECDHPublicKey {
|
||||
|
Loading…
Reference in New Issue
Block a user