Commit 0b667ba3 authored by Him188's avatar Him188

Sealed messages

parent 47e35707
......@@ -2,10 +2,7 @@ package net.mamoe.mirai.contact
import kotlinx.coroutines.CompletableDeferred
import net.mamoe.mirai.Bot
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.message.defaults.PlainText
import net.mamoe.mirai.message.defaults.UnsolvedImage
import net.mamoe.mirai.message.*
import net.mamoe.mirai.network.LoginSession
/**
......@@ -43,8 +40,5 @@ abstract class Contact internal constructor(val bot: Bot, val number: Long) {
this.sendMessage(MessageChain(message))
}
/**
* Async
*/
abstract suspend fun sendXMLMessage(message: String)
}
......@@ -2,7 +2,7 @@ package net.mamoe.mirai.contact
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group.Companion.groupNumberToId
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.utils.ContactList
import java.io.Closeable
......
package net.mamoe.mirai.contact
import net.mamoe.mirai.Bot
import net.mamoe.mirai.message.At
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.defaults.At
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.message.MessageChain
/**
* QQ 账号.
......
@file:Suppress("unused")
package net.mamoe.mirai.event
import net.mamoe.mirai.event.internal.Handler
......
......@@ -3,7 +3,7 @@ package net.mamoe.mirai.event.events
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.message.MessageChain
/**
* @author Him188moe
......
......@@ -4,7 +4,7 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.message.MessageChain
/**
* @author Him188moe
......
......@@ -13,6 +13,7 @@ import net.mamoe.mirai.utils.BotAccount
import net.mamoe.mirai.utils.Console
import kotlin.reflect.KClass
import kotlin.reflect.full.allSuperclasses
import kotlin.reflect.full.isSuperclassOf
/**
* 监听和广播实现
......@@ -75,8 +76,8 @@ internal suspend fun <E : Event> E.broadcastInternal(): E {
callListeners(this::class.listeners as EventListeners<in E>)
this::class.allSuperclasses.forEach {
//println("super: " + it.simpleName)
//todo multi platform
if (Event::class.java.isAssignableFrom(it.java)) {
if (Event::class.isSuperclassOf(it)) {
callListeners((it as KClass<out Event>).listeners as EventListeners<in E>)
}
}
......
......@@ -2,7 +2,6 @@ package net.mamoe.mirai.message
/**
* @author LamGC
* @author Him188moe
*/
@Suppress("EnumEntryName", "unused", "SpellCheckingInspection")
enum class FaceID constructor(val id: Int) {
......
@file:Suppress("MemberVisibilityCanBePrivate")
package net.mamoe.mirai.message
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.defaults.*
import java.awt.image.BufferedImage
import java.io.File
import java.util.*
......@@ -24,150 +24,109 @@ import java.util.*
* qq.sendMessage(message + "world")
* ```
*
* @author Him188moe
* 但注意: 不能 `String + Message`. 只能 `Message + String`
*
* @see Contact.sendMessage
*/
abstract class Message {
internal abstract val type: MessageKey
private var toStringCache: String? = null
private val cacheLock = object : Any() {}
internal abstract fun toStringImpl(): String
sealed class Message {
/**
* 得到用户层的文本消息. 如:
* - [PlainText] 得到 消息内容
* - [Image] 得到 "{ID}.png"
* - [At] 得到 "[@qq]"
* 易读的 [String] 值
* 如:
* ```
* [@123456789]
* [face123]
* ```
*/
final override fun toString(): String {
synchronized(cacheLock) {
if (toStringCache != null) {
return toStringCache!!
}
this.toStringCache = toStringImpl()
return toStringCache!!
}
}
abstract val stringValue: String
internal fun clearToStringCache() {
synchronized(cacheLock) {
toStringCache = null
}
}
final override fun toString(): String = stringValue
/**
* 得到类似 "PlainText(内容)", "Image(ID)"
*/
open fun toObjectString(): String {
return this.javaClass.simpleName + String.format("(%s)", this.toString())
}
infix fun eq(other: Message): Boolean = this == other
/**
* 转换为数据包使用的 byte array
* 将 [stringValue] 与 [other] 比较
*/
abstract fun toByteArray(): ByteArray
infix fun eq(other: String): Boolean = this.stringValue == other
/**
* 比较两个 Message 的内容是否相等. 如:
* - [PlainText] 比较 [PlainText.text]
* - [Image] 比较 [Image.imageId]
*/
abstract infix fun eq(another: Message): Boolean
/**
* 将这个消息的 [toString] 与 [another] 比较
*/
infix fun eq(another: String): Boolean = this.toString() == another
/**
* 判断 [sub] 是否存在于本消息中
*/
abstract operator fun contains(sub: String): Boolean
/**
* 把这个消息连接到另一个消息的头部. 相当于字符串相加
*
*
* Connects this Message to the head of another Message.
* That is, another message becomes the tail of this message.
* This method does similar to [String.concat]
*
*
* E.g.:
* PlainText a = new PlainText("Hello ");
* PlainText b = new PlainText("world");
* PlainText c = a.concat(b);
*
*
* the text of c is "Hello world"
*
* @param tail tail
* @return message connected
* 把这个消息连接到另一个消息的头部. 类似于字符串相加
*/
open fun concat(tail: Message): MessageChain {
if (tail is MessageChain) {
return MessageChain(this).let {
tail.list.forEach { child -> it.concat(child) }
it
}
}
return MessageChain(this, Objects.requireNonNull(tail))
}
open fun concat(tail: Message): MessageChain =
if (tail is MessageChain) MessageChain(this).also { tail.list.forEach { child -> it.concat(child) } }
else MessageChain(this, Objects.requireNonNull(tail))
fun concat(tail: String): MessageChain {
return concat(PlainText(tail))
}
infix operator fun plus(another: Message): MessageChain = this.concat(another)
infix operator fun plus(another: String): MessageChain = this.concat(another.toMessage())
infix operator fun plus(another: Number): MessageChain = this.concat(another.toString().toMessage())
}
data class PlainText(override val stringValue: String) : Message() {
override operator fun contains(sub: String): Boolean = this.stringValue.contains(sub)
}
infix fun withImage(imageId: String): MessageChain = this + Image(imageId)
fun withImage(filename: String, image: BufferedImage): MessageChain = this + UnsolvedImage(filename, image)
infix fun withImage(imageFile: File): MessageChain = this + UnsolvedImage(imageFile)
/**
* 图片消息.
* 由接收消息时构建, 可直接发送
*
* @param imageId 类似 `{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg`. 群的是大写id, 好友的是小写id
*/
data class Image(val imageId: String) : Message() {
override val stringValue: String = "[$imageId]"
override operator fun contains(sub: String): Boolean = false //No string can be contained in a image
}
infix fun withAt(target: QQ): MessageChain = this + target.at()
infix fun withAt(target: Long): MessageChain = this + At(target)
/**
* At 一个人
*/
data class At(val target: Long) : Message() {
constructor(target: QQ) : this(target.number)
override val stringValue: String = "[@$target]"
override operator fun contains(sub: String): Boolean = false
}
open fun toChain(): MessageChain {
return MessageChain(this)
}
/**
* QQ 自带表情
*/
data class Face(val id: FaceID) : Message() {
override val stringValue: String = "[face${id.id}]"
override operator fun contains(sub: String): Boolean = false
}
data class MessageChain(
/**
* Elements will not be instances of [MessageChain]
*/
val list: MutableList<Message>
) : Message(), Iterable<Message> {
constructor() : this(mutableListOf())
constructor(vararg messages: Message) : this(mutableListOf(*messages))
constructor(messages: Iterable<Message>) : this(messages.toMutableList())
/* For Kotlin */
val size: Int = list.size
/**
* 实现使用 '+' 操作符连接 [Message] 与 [Message]
*/
infix operator fun plus(another: Message): MessageChain = this.concat(another)
override val stringValue: String get() = this.list.joinToString("") { it.stringValue }
/**
* 实现使用 '+' 操作符连接 [Message] 与 [String]
*/
infix operator fun plus(another: String): MessageChain = this.concat(another)
override fun iterator(): Iterator<Message> = this.list.iterator()
/**
* 实现使用 '+' 操作符连接 [Message] 与 [Number]
* 获取第一个 [M] 类型的实例
* @throws [NoSuchElementException] 如果找不到该类型的实例
*/
infix operator fun plus(another: Number): MessageChain = this.concat(another.toString())
inline fun <reified M : Message> first(): Message = this.list.first { M::class.isInstance(it) }
/**
* 连接 [String] 与 [Message]
* 获取第一个 [M] 类型的实例
*/
fun String.concat(another: Message): MessageChain = PlainText(this).concat(another)
override fun hashCode(): Int {
return javaClass.hashCode()
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Message) return false
inline fun <reified M : Message> firstOrNull(): Message? = this.list.firstOrNull { M::class.isInstance(it) }
if (type != other.type) return false
override operator fun contains(sub: String): Boolean = list.any { it.contains(sub) }
return this.toString() == other.toString()
override fun concat(tail: Message): MessageChain {
if (tail is MessageChain) tail.list.forEach { child -> this.concat(child) }
else this.list.add(tail)
return this
}
}
\ No newline at end of file
package net.mamoe.mirai.message
/**
* @author Him188moe
*/
open class MessageKey(
val intValue: Int
)
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.message
/**
* @author Him188moe
*/
@Suppress("unused")
enum class MessageType(private val value: UByte) {
PLAIN_TEXT(0x03u),
AT(0x06u),
FACE(0x02u),
IMAGE(0x03u),//may be 0x06?
;
val intValue: Int = this.value.toInt()
}
\ No newline at end of file
package net.mamoe.mirai.message
// Message 扩展方法
/**
* 构造 [PlainText]
*/
fun String.toMessage(): PlainText = PlainText(this)
/**
* 用 `this` 构造 [MessageChain]
*/
fun Message.toChain(): MessageChain = if (this is MessageChain) this else MessageChain(this)
fun MessageChain.containsType(clazz: Class<out Message>): Boolean = list.any { clazz.isInstance(it) }
operator fun MessageChain.contains(sub: Class<out Message>): Boolean = containsType(sub)
\ No newline at end of file
package net.mamoe.mirai.message.defaults
package net.mamoe.mirai.message
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
......@@ -25,7 +25,7 @@ import javax.imageio.ImageIO
* @suppress todo 重新设计
* @author Him188moe
*/
class UnsolvedImage(filename: String, val image: BufferedImage) : Image(getImageId(filename)) {
class UnsolvedImage(private val filename: String, val image: BufferedImage) {
constructor(imageFile: File) : this(imageFile.name, ImageIO.read(imageFile))
constructor(url: URL) : this(File(url.file))
......@@ -52,6 +52,10 @@ class UnsolvedImage(filename: String, val image: BufferedImage) : Image(getImage
}
}
fun toImage(): Image {
return Image(getImageId(filename))
}
companion object {
@JvmStatic
......
package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageKey
/**
* At 一个人
*
* @author Him188moe
*/
class At(val target: Long) : Message() {
companion object Key : MessageKey(0x06)
override val type: MessageKey = Key
constructor(target: QQ) : this(target.number)
override fun toStringImpl(): String = "[@$target]"
override fun toByteArray(): ByteArray {
TODO()
}
override operator fun contains(sub: String): Boolean = false
override fun eq(another: Message): Boolean {
if (another !is At) {
return false
}
return another.target == this.target
}
}
package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.message.FaceID
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageKey
import net.mamoe.mirai.network.protocol.tim.packet.readLVNumber
import net.mamoe.mirai.network.protocol.tim.packet.writeHex
import net.mamoe.mirai.network.protocol.tim.packet.writeLVByteArray
import net.mamoe.mirai.utils.dataDecode
import net.mamoe.mirai.utils.dataEncode
/**
* QQ 自带表情
*
* @author Him188moe
*/
class Face(val id: FaceID) : Message() {
companion object Key : MessageKey(0x02)
override val type: MessageKey = Key
override fun toStringImpl(): String {
return "[face${id.id}]"
}
override fun toObjectString(): String {
return "Face[$id]"
}
override fun toByteArray(): ByteArray = dataEncode { section ->
section.writeByte(this.type.intValue)
section.writeLVByteArray(dataEncode { child ->
child.writeShort(1)
child.writeByte(this.id.id)
child.writeHex("0B 00 08 00 01 00 04 52 CC F5 D0 FF")
child.writeShort(2)
child.writeByte(0x14)//??
child.writeByte(this.id.id + 65)
})
}
override fun eq(another: Message): Boolean {
if (another !is Face) {
return false
}
return this.id == another.id
}
override operator fun contains(sub: String): Boolean = false
object PacketHelper {
fun ofByteArray(data: ByteArray): Face = dataDecode(data) {
//00 01 AF 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 F0
//00 01 0C 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 4D
it.skip(1)
val id1 = FaceID.ofId(it.readLVNumber().toInt())//可能这个是id, 也可能下面那个
it.skip(it.readByte().toLong())
it.readLVNumber()//某id?
return@dataDecode Face(id1)
}
}
}
package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageKey
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.dataDecode
import net.mamoe.mirai.utils.dataEncode
import net.mamoe.mirai.utils.skip
import net.mamoe.mirai.utils.toUHexString
/**
* 图片消息.
* 由接收消息时构建, 可直接发送
*
* @param imageId 类似 `{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg`. 群的是大写id, 好友的是小写id
*
* @author Him188moe
*/
open class Image(val imageId: String) : Message() {
companion object Key : MessageKey(0x03)
override val type: MessageKey = Key
override fun toStringImpl(): String {
return "[$imageId]"
}
override fun toObjectString(): String {
return "Image[$imageId]"
}
override fun toByteArray(): ByteArray = dataEncode { section ->
section.writeByte(intValue)
section.writeLVByteArray(dataEncode { child ->
child.writeByte(0x02)
child.writeLVString(this.imageId)
child.writeHex("04 00 " +
"04 9B 53 B0 08 " +
"05 00 " +
"04 D9 8A 5A 70 " +
"06 00 " +
"04 00 00 00 50 " +
"07 00 " +
"01 43 08 00 00 09 00 01 01 0B 00 00 14 00 04 11 00 00 00 15 00 04 00 00 02 BC 16 00 04 00 00 02 BC 18 00 04 00 00 7D 5E FF 00 5C 15 36 20 39 32 6B 41 31 43 39 62 35 33 62 30 30 38 64 39 38 61 35 61 37 30 20")
child.writeHex("20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20")
child.writeBytes(this.imageId)
child.writeByte(0x41)
})
}
override fun eq(another: Message): Boolean {
if (another is Image) {
return this.imageId == another.imageId
}
return false
}
override operator fun contains(sub: String): Boolean = false //No string can be contained in a image
object PacketHelper {
@JvmStatic
fun ofByteArray0x06(data: ByteArray): Image = dataDecode(data) {
it.skip(1)
println("好友的图片")
println(data.toUHexString())
val filenameLength = it.readShort()
val suffix = it.readString(filenameLength).substringAfter(".")
it.skip(data.size - 37 - 1 - filenameLength - 2)
val imageId = String(it.readNBytes(36))
println(imageId)
it.skip(1)//0x41
return@dataDecode Image("{$imageId}.$suffix")
}
@JvmStatic
fun ofByteArray0x03(data: ByteArray): Image = dataDecode(data) {
it.skip(1)
return@dataDecode Image(String(it.readLVByteArray()))
/*
println(String(it.readLVByteArray()))
it.readTLVMap()
return@dataDecode Image(String(it.readLVByteArray().cutTail(5).getRight(42)))
/
it.skip(data.size - 47)
val imageId = String(it.readNBytes(42))
it.skip(1)//0x41
it.skip(1)//0x42
it.skip(1)//0x43
it.skip(1)//0x41
return@dataDecode Image(imageId)*/
}
}
}
\ No newline at end of file
package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageKey
import net.mamoe.mirai.network.protocol.tim.packet.readLVByteArray
import net.mamoe.mirai.network.protocol.tim.packet.readNBytes
import net.mamoe.mirai.utils.dataDecode
import net.mamoe.mirai.utils.dataEncode
import net.mamoe.mirai.utils.toUHexString
import java.io.DataInputStream
import java.util.*
import java.util.stream.Collectors
import java.util.stream.Stream
import kotlin.reflect.KClass
class MessageChain : Message {
companion object Key : MessageKey(0xff)//only used to compare
override val type: MessageKey = Key
/**
* Elements will not be instances of [MessageChain]
*/
val list: MutableList<Message> = Collections.synchronizedList(LinkedList<Message>())
constructor(head: Message, tail: Message) {
Objects.requireNonNull(head)
Objects.requireNonNull(tail)
list.add(head)
list.add(tail)
}
constructor(message: Message) {
Objects.requireNonNull(message)
list.add(message)
}
constructor(messages: Collection<Message>) {
list.addAll(messages)
}
constructor()
/**
* 获取第一个这个类型的消息
*/
operator fun get(type: MessageKey): Message? = list.firstOrNull { it.type == type }
fun size(): Int {
return list.size
}
fun containsType(clazz: KClass<out Message>): Boolean = list.any { clazz.isInstance(it) }
fun containsType(clazz: Class<out Message>): Boolean = list.any { clazz.isInstance(it) }
operator fun contains(sub: KClass<out Message>): Boolean = containsType(sub)
operator fun contains(sub: Class<out Message>): Boolean = containsType(sub)
fun stream(): Stream<Message> {
return list.stream()
}
override fun toStringImpl(): String {
return this.list.stream().map { it.toString() }.collect(Collectors.joining(""))
}
override fun toObjectString(): String {
return String.format("MessageChain(%s)", this.list.stream().map { it.toObjectString() }.collect(Collectors.joining(", ")))
}
override fun concat(tail: Message): MessageChain {
if (tail is MessageChain) {
tail.list.forEach { child -> this.concat(child) }
return this
}
this.list.add(tail)
clearToStringCache()
return this
}
override fun toChain(): MessageChain {
return this
}
override fun toByteArray(): ByteArray = dataEncode {
stream().forEach { message ->
it.write(message.toByteArray())
}
}
override fun eq(another: Message): Boolean {
if (another !is MessageChain) {
return false
}
return this.list == another.list
}
override operator fun contains(sub: String): Boolean = list.any { it.contains(sub) }
operator fun component1(): Message = this.list[0]
operator fun component2(): Message = this.list[1]
operator fun component3(): Message = this.list[2]
object PacketHelper {
@JvmStatic
fun ofByteArray(byteArray: ByteArray): MessageChain = dataDecode(byteArray) {
it.readMessageChain()
}
}
}
fun DataInputStream.readMessage(): Message? {
val messageType = this.readByte().toInt()
val sectionLength = this.readShort().toLong()//sectionLength: short
val sectionData = this.readNBytes(sectionLength)
return when (messageType) {
0x01 -> PlainText.PacketHelper.ofByteArray(sectionData)
0x02 -> Face.PacketHelper.ofByteArray(sectionData)
0x03 -> Image.PacketHelper.ofByteArray0x03(sectionData)
0x06 -> Image.PacketHelper.ofByteArray0x06(sectionData)
0x19 -> {//长文本
val value = readLVByteArray()
//todo 未知压缩算法
PlainText(String(value))
// PlainText(String(GZip.uncompress( value)))
}
0x14 -> {//长文本
val value = readLVByteArray()
println(value.size)
println(value.toUHexString())
//todo 未知压缩算法
this.skip(7)//几个TLV
return PlainText(String(value))
}
0x0E -> {
//null
null
}
else -> {
println("未知的messageType=0x${messageType.toByte().toUHexString()}")
println("后文=${this.readAllBytes().toUHexString()}")
null
}
}
}
fun DataInputStream.readMessageChain(): MessageChain {
val chain = MessageChain()
var got: Message? = null
do {
if (got != null) {
chain.concat(got)
}
if (this.available() == 0) {
return chain
}
got = this.readMessage()
} while (got != null)
return chain
}
\ No newline at end of file
package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageKey
import net.mamoe.mirai.network.protocol.tim.packet.readLVString
import net.mamoe.mirai.network.protocol.tim.packet.writeLVByteArray
import net.mamoe.mirai.network.protocol.tim.packet.writeLVString
import net.mamoe.mirai.utils.dataDecode
import net.mamoe.mirai.utils.dataEncode
/**
* @author Him188moe
*/
class PlainText(private val text: String) : Message() {
companion object Key : MessageKey(0x01)
override val type: MessageKey = Key
override fun toStringImpl(): String {
return text
}
override fun toByteArray(): ByteArray = dataEncode { section ->
section.writeByte(this.type.intValue)
section.writeLVByteArray(dataEncode { child ->
child.writeByte(0x01)
child.writeLVString(this.text)
})
}
override fun eq(another: Message): Boolean {
if (another !is PlainText) {
return false
}
return this.text == another.text
}
override operator fun contains(sub: String): Boolean = this.toString().contains(sub)
object PacketHelper {
@JvmStatic
fun ofByteArray(data: ByteArray): PlainText = dataDecode(data) {
it.skip(1)
PlainText(it.readLVString())
}
}
}
package net.mamoe.mirai.message.internal
import net.mamoe.mirai.message.*
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.*
import java.io.DataInputStream
internal fun ByteArray.parseMessageFace(): Face = dataDecode(this) {
//00 01 AF 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 F0
//00 01 0C 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 4D
it.skip(1)
val id1 = FaceID.ofId(it.readLVNumber().toInt())//可能这个是id, 也可能下面那个
it.skip(it.readByte().toLong())
it.readLVNumber()//某id?
return@dataDecode Face(id1)
}
internal fun ByteArray.parsePlainText(): PlainText = dataDecode(this) {
it.skip(1)
PlainText(it.readLVString())
}
internal fun ByteArray.parseMessageImage0x06(): Image = dataDecode(this) {
it.skip(1)
MiraiLogger.debug("好友的图片")
MiraiLogger.debug(this.toUHexString())
val filenameLength = it.readShort()
val suffix = it.readString(filenameLength).substringAfter(".")
it.skip(this.size - 37 - 1 - filenameLength - 2)
val imageId = String(it.readNBytes(36))
MiraiLogger.debug(imageId)
it.skip(1)//0x41
return@dataDecode Image("{$imageId}.$suffix")
}
internal fun ByteArray.parseMessageImage0x03(): Image = dataDecode(this) {
it.skip(1)
return@dataDecode Image(String(it.readLVByteArray()))
/*
println(String(it.readLVByteArray()))
it.readTLVMap()
return@dataDecode Image(String(it.readLVByteArray().cutTail(5).getRight(42)))
/
it.skip(data.size - 47)
val imageId = String(it.readNBytes(42))
it.skip(1)//0x41
it.skip(1)//0x42
it.skip(1)//0x43
it.skip(1)//0x41
return@dataDecode Image(imageId)*/
}
internal fun ByteArray.parseMessageChain(): MessageChain = dataDecode(this) {
it.readMessageChain()
}
internal fun DataInputStream.readMessage(): Message? {
val messageType = this.readByte().toInt()
val sectionLength = this.readShort().toLong()//sectionLength: short
val sectionData = this.readNBytes(sectionLength)
return when (messageType) {
0x01 -> sectionData.parsePlainText()
0x02 -> sectionData.parseMessageFace()
0x03 -> sectionData.parseMessageImage0x03()
0x06 -> sectionData.parseMessageImage0x06()
0x19 -> {//长文本
val value = readLVByteArray()
//todo 未知压缩算法
PlainText(String(value))
// PlainText(String(GZip.uncompress( value)))
}
0x14 -> {//长文本
val value = readLVByteArray()
println(value.size)
println(value.toUHexString())
//todo 未知压缩算法
this.skip(7)//几个TLV
return PlainText(String(value))
}
0x0E -> {
//null
null
}
else -> {
println("未知的messageType=0x${messageType.toByte().toUHexString()}")
println("后文=${this.readAllBytes().toUHexString()}")
null
}
}
}
fun DataInputStream.readMessageChain(): MessageChain {
val chain = MessageChain()
var got: Message? = null
do {
if (got != null) {
chain.concat(got)
}
if (this.available() == 0) {
return chain
}
got = this.readMessage()
} while (got != null)
return chain
}
fun MessageChain.toByteArray(): ByteArray = dataEncode { result ->
this@toByteArray.list.forEach { message ->
result.write(with(message) {
when (this) {
is Face -> dataEncode { section ->
section.writeByte(MessageType.FACE.intValue)
section.writeLVByteArray(dataEncode { child ->
child.writeShort(1)
child.writeByte(this.id.id)
child.writeHex("0B 00 08 00 01 00 04 52 CC F5 D0 FF")
child.writeShort(2)
child.writeByte(0x14)//??
child.writeByte(this.id.id + 65)
})
}
is At -> throw UnsupportedOperationException("At is not supported now but is expecting to be supported")
is Image -> dataEncode { section ->
section.writeByte(MessageType.IMAGE.intValue)
section.writeLVByteArray(dataEncode { child ->
child.writeByte(0x02)
child.writeLVString(this.imageId)
child.writeHex("04 00 " +
"04 9B 53 B0 08 " +
"05 00 " +
"04 D9 8A 5A 70 " +
"06 00 " +
"04 00 00 00 50 " +
"07 00 " +
"01 43 08 00 00 09 00 01 01 0B 00 00 14 00 04 11 00 00 00 15 00 04 00 00 02 BC 16 00 04 00 00 02 BC 18 00 04 00 00 7D 5E FF 00 5C 15 36 20 39 32 6B 41 31 43 39 62 35 33 62 30 30 38 64 39 38 61 35 61 37 30 20")
child.writeHex("20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20")
child.writeBytes(this.imageId)
child.writeByte(0x41)
})
}
is PlainText -> dataEncode { section ->
section.writeByte(MessageType.PLAIN_TEXT.intValue)
section.writeLVByteArray(dataEncode { child ->
child.writeByte(0x01)
child.writeLVString(this.stringValue)
})
}
else -> throw UnsupportedOperationException("${this::class.simpleName} is not supported")
}
})
}
}
\ No newline at end of file
......@@ -7,7 +7,7 @@ import net.mamoe.mirai.event.events.FriendMessageEvent
import net.mamoe.mirai.event.events.GroupMessageEvent
import net.mamoe.mirai.getGroupByNumber
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.ServerFriendMessageEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerGroupMessageEventPacket
......
......@@ -192,32 +192,30 @@ fun md5(str: String): ByteArray = MessageDigest.getInstance("MD5").digest(str.to
fun md5(byteArray: ByteArray): ByteArray = MessageDigest.getInstance("MD5").digest(byteArray)
@Throws(IOException::class)
fun DataOutputStream.writeZero(count: Int) {
repeat(count) {
this.writeByte(0)
}
}
@Throws(IOException::class)
fun DataOutputStream.writeRandom(length: Int) {
repeat(length) {
this.writeByte((Math.random() * 255).toInt())
}
}
@Throws(IOException::class)
fun DataOutputStream.writeQQ(qq: Long) {
this.write(qq.toUInt().toByteArray())
}
@Throws(IOException::class)
fun DataOutputStream.writeGroup(groupIdOrGroupNumber: Long) {
this.write(groupIdOrGroupNumber.toUInt().toByteArray())
}
fun DataOutputStream.writeUByte(uByte: UByte) {
this.write(uByte.toInt())
}
fun DataOutputStream.writeLVByteArray(byteArray: ByteArray) {
this.writeShort(byteArray.size)
this.write(byteArray)
......
......@@ -2,8 +2,8 @@
package net.mamoe.mirai.network.protocol.tim.packet
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.message.defaults.readMessageChain
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.internal.readMessageChain
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.utils.dataDecode
import net.mamoe.mirai.utils.hexToBytes
......
package net.mamoe.mirai.network.protocol.tim.packet.action
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.internal.toByteArray
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.dataEncode
......
package net.mamoe.mirai.network.protocol.tim.packet.action
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.internal.toByteArray
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.dataEncode
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.utils
import com.sun.jna.Library
import com.sun.jna.Native
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import sun.misc.Unsafe
/**
* @author Him188moe
*/
object ECDH : IECDH by Native.load("ecdhdll64", IECDH::class.java)
interface IECDH : Library {
//fun encrypt(publicKey: UByteArray, shaKey: UByteArray): UByteArray
fun encrypt(publicKey: Long, shaKey: Long): Long
}
fun main() {
//
// ECDH.encrypt(TIMProtocol.publicKey.hexToUBytes(), TIMProtocol.key0836.hexToUBytes())
val unsafe = Unsafe::class.java.getDeclaredField("theUnsafe").also { it.trySetAccessible() }.get(null) as Unsafe
val publicKeyAddress = unsafe.allocateMemory(25)
TIMProtocol.publicKey.hexToUBytes().forEachIndexed { index, value ->
unsafe.setMemory(publicKeyAddress + index, 1, value.toByte())
}
val key0836Address = unsafe.allocateMemory(16)
TIMProtocol.key0836.hexToUBytes().forEachIndexed { index, value ->
unsafe.setMemory(key0836Address + index, 1, value.toByte())
}
val encrypt = ECDH.encrypt(publicKeyAddress, key0836Address)
//
val bytes = mutableListOf<Byte>()
repeat(16) {
bytes += unsafe.getByte(encrypt + it)
}
println(bytes.toByteArray().toUHexString())
}
......@@ -3,7 +3,7 @@
import jpcap.JpcapCaptor
import jpcap.packet.IPPacket
import jpcap.packet.UDPPacket
import net.mamoe.mirai.message.defaults.readMessageChain
import net.mamoe.mirai.message.internal.readMessageChain
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
......@@ -151,7 +151,7 @@ object Main {
+ 1
)
val chain = it.readMessageChain()
println(chain.toObjectString())
println(chain)
}
}
......
This diff is collapsed.
......@@ -8,8 +8,8 @@ import net.mamoe.mirai.event.subscribeAll
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.event.subscribeUntilFalse
import net.mamoe.mirai.login
import net.mamoe.mirai.message.defaults.Image
import net.mamoe.mirai.message.defaults.PlainText
import net.mamoe.mirai.message.Image
import net.mamoe.mirai.message.PlainText
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
import net.mamoe.mirai.utils.BotAccount
import net.mamoe.mirai.utils.Console
......@@ -26,7 +26,7 @@ suspend fun main() {
), Console())
bot.login().let {
if(it != LoginState.SUCCESS) {
if (it != LoginState.SUCCESS) {
MiraiLogger.error("Login failed: " + it.name)
exitProcess(0)
}
......@@ -36,10 +36,10 @@ suspend fun main() {
//提供泛型以监听事件
subscribeAlways<FriendMessageEvent> {
//获取第一个纯文本消息
val firstText = it.message[PlainText]
val firstText = it.message.first<PlainText>()
//获取第一个图片
val firstImage = it.message[Image]
val firstImage = it.message.first<Image>()
when {
it.message eq "你好" -> it.reply("你好!")
......@@ -78,7 +78,7 @@ suspend fun main() {
FriendMessageEvent::class.subscribeAll {
always {
//获取第一个纯文本消息
val firstText = it.message[PlainText]
val firstText = it.message.first<PlainText>()
}
}
......
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