Commit 0b667ba3 authored by Him188's avatar Him188

Sealed messages

parent 47e35707
...@@ -2,10 +2,7 @@ package net.mamoe.mirai.contact ...@@ -2,10 +2,7 @@ package net.mamoe.mirai.contact
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.message.Message import net.mamoe.mirai.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.network.LoginSession import net.mamoe.mirai.network.LoginSession
/** /**
...@@ -43,8 +40,5 @@ abstract class Contact internal constructor(val bot: Bot, val number: Long) { ...@@ -43,8 +40,5 @@ abstract class Contact internal constructor(val bot: Bot, val number: Long) {
this.sendMessage(MessageChain(message)) this.sendMessage(MessageChain(message))
} }
/**
* Async
*/
abstract suspend fun sendXMLMessage(message: String) abstract suspend fun sendXMLMessage(message: String)
} }
...@@ -2,7 +2,7 @@ package net.mamoe.mirai.contact ...@@ -2,7 +2,7 @@ package net.mamoe.mirai.contact
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group.Companion.groupNumberToId 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 net.mamoe.mirai.utils.ContactList
import java.io.Closeable import java.io.Closeable
......
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.message.At
import net.mamoe.mirai.message.Message import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.defaults.At import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.defaults.MessageChain
/** /**
* QQ 账号. * QQ 账号.
......
@file:Suppress("unused")
package net.mamoe.mirai.event package net.mamoe.mirai.event
import net.mamoe.mirai.event.internal.Handler import net.mamoe.mirai.event.internal.Handler
......
...@@ -3,7 +3,7 @@ package net.mamoe.mirai.event.events ...@@ -3,7 +3,7 @@ package net.mamoe.mirai.event.events
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.Message import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.defaults.MessageChain import net.mamoe.mirai.message.MessageChain
/** /**
* @author Him188moe * @author Him188moe
......
...@@ -4,7 +4,7 @@ import net.mamoe.mirai.Bot ...@@ -4,7 +4,7 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.Message import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.defaults.MessageChain import net.mamoe.mirai.message.MessageChain
/** /**
* @author Him188moe * @author Him188moe
......
...@@ -13,6 +13,7 @@ import net.mamoe.mirai.utils.BotAccount ...@@ -13,6 +13,7 @@ import net.mamoe.mirai.utils.BotAccount
import net.mamoe.mirai.utils.Console import net.mamoe.mirai.utils.Console
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.full.allSuperclasses import kotlin.reflect.full.allSuperclasses
import kotlin.reflect.full.isSuperclassOf
/** /**
* 监听和广播实现 * 监听和广播实现
...@@ -75,8 +76,8 @@ internal suspend fun <E : Event> E.broadcastInternal(): E { ...@@ -75,8 +76,8 @@ internal suspend fun <E : Event> E.broadcastInternal(): E {
callListeners(this::class.listeners as EventListeners<in E>) callListeners(this::class.listeners as EventListeners<in E>)
this::class.allSuperclasses.forEach { this::class.allSuperclasses.forEach {
//println("super: " + it.simpleName) //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>) callListeners((it as KClass<out Event>).listeners as EventListeners<in E>)
} }
} }
......
...@@ -2,7 +2,6 @@ package net.mamoe.mirai.message ...@@ -2,7 +2,6 @@ package net.mamoe.mirai.message
/** /**
* @author LamGC * @author LamGC
* @author Him188moe
*/ */
@Suppress("EnumEntryName", "unused", "SpellCheckingInspection") @Suppress("EnumEntryName", "unused", "SpellCheckingInspection")
enum class FaceID constructor(val id: Int) { enum class FaceID constructor(val id: Int) {
......
@file:Suppress("MemberVisibilityCanBePrivate")
package net.mamoe.mirai.message package net.mamoe.mirai.message
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.defaults.*
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.io.File
import java.util.* import java.util.*
...@@ -24,150 +24,109 @@ import java.util.* ...@@ -24,150 +24,109 @@ import java.util.*
* qq.sendMessage(message + "world") * qq.sendMessage(message + "world")
* ``` * ```
* *
* @author Him188moe * 但注意: 不能 `String + Message`. 只能 `Message + String`
*
* @see Contact.sendMessage * @see Contact.sendMessage
*/ */
abstract class Message { sealed class Message {
internal abstract val type: MessageKey
private var toStringCache: String? = null
private val cacheLock = object : Any() {}
internal abstract fun toStringImpl(): String
/** /**
* 得到用户层的文本消息. 如: * 易读的 [String] 值
* - [PlainText] 得到 消息内容 * 如:
* - [Image] 得到 "{ID}.png" * ```
* - [At] 得到 "[@qq]" * [@123456789]
* [face123]
* ```
*/ */
final override fun toString(): String { abstract val stringValue: String
synchronized(cacheLock) {
if (toStringCache != null) {
return toStringCache!!
}
this.toStringCache = toStringImpl()
return toStringCache!!
}
}
internal fun clearToStringCache() { final override fun toString(): String = stringValue
synchronized(cacheLock) {
toStringCache = null
}
}
/** infix fun eq(other: Message): Boolean = this == other
* 得到类似 "PlainText(内容)", "Image(ID)"
*/
open fun toObjectString(): String {
return this.javaClass.simpleName + String.format("(%s)", this.toString())
}
/** /**
* 转换为数据包使用的 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 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 { open fun concat(tail: Message): MessageChain =
if (tail is MessageChain) { if (tail is MessageChain) MessageChain(this).also { tail.list.forEach { child -> it.concat(child) } }
return MessageChain(this).let { else MessageChain(this, Objects.requireNonNull(tail))
tail.list.forEach { child -> it.concat(child) }
it
}
}
return MessageChain(this, Objects.requireNonNull(tail))
}
fun concat(tail: String): MessageChain { infix operator fun plus(another: Message): MessageChain = this.concat(another)
return concat(PlainText(tail)) 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
/** override val stringValue: String get() = this.list.joinToString("") { it.stringValue }
* 实现使用 '+' 操作符连接 [Message] 与 [Message]
*/
infix operator fun plus(another: Message): MessageChain = this.concat(another)
/** override fun iterator(): Iterator<Message> = this.list.iterator()
* 实现使用 '+' 操作符连接 [Message] 与 [String]
*/
infix operator fun plus(another: String): MessageChain = this.concat(another)
/** /**
* 实现使用 '+' 操作符连接 [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) inline fun <reified M : Message> firstOrNull(): Message? = this.list.firstOrNull { M::class.isInstance(it) }
override fun hashCode(): Int {
return javaClass.hashCode()
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Message) return false
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.CompletableDeferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
...@@ -25,7 +25,7 @@ import javax.imageio.ImageIO ...@@ -25,7 +25,7 @@ import javax.imageio.ImageIO
* @suppress todo 重新设计 * @suppress todo 重新设计
* @author Him188moe * @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(imageFile: File) : this(imageFile.name, ImageIO.read(imageFile))
constructor(url: URL) : this(File(url.file)) constructor(url: URL) : this(File(url.file))
...@@ -52,6 +52,10 @@ class UnsolvedImage(filename: String, val image: BufferedImage) : Image(getImage ...@@ -52,6 +52,10 @@ class UnsolvedImage(filename: String, val image: BufferedImage) : Image(getImage
} }
} }
fun toImage(): Image {
return Image(getImageId(filename))
}
companion object { companion object {
@JvmStatic @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 ...@@ -7,7 +7,7 @@ import net.mamoe.mirai.event.events.FriendMessageEvent
import net.mamoe.mirai.event.events.GroupMessageEvent import net.mamoe.mirai.event.events.GroupMessageEvent
import net.mamoe.mirai.getGroupByNumber import net.mamoe.mirai.getGroupByNumber
import net.mamoe.mirai.getQQ 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.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.ServerFriendMessageEventPacket import net.mamoe.mirai.network.protocol.tim.packet.ServerFriendMessageEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerGroupMessageEventPacket import net.mamoe.mirai.network.protocol.tim.packet.ServerGroupMessageEventPacket
......
...@@ -192,32 +192,30 @@ fun md5(str: String): ByteArray = MessageDigest.getInstance("MD5").digest(str.to ...@@ -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) fun md5(byteArray: ByteArray): ByteArray = MessageDigest.getInstance("MD5").digest(byteArray)
@Throws(IOException::class)
fun DataOutputStream.writeZero(count: Int) { fun DataOutputStream.writeZero(count: Int) {
repeat(count) { repeat(count) {
this.writeByte(0) this.writeByte(0)
} }
} }
@Throws(IOException::class)
fun DataOutputStream.writeRandom(length: Int) { fun DataOutputStream.writeRandom(length: Int) {
repeat(length) { repeat(length) {
this.writeByte((Math.random() * 255).toInt()) this.writeByte((Math.random() * 255).toInt())
} }
} }
@Throws(IOException::class)
fun DataOutputStream.writeQQ(qq: Long) { fun DataOutputStream.writeQQ(qq: Long) {
this.write(qq.toUInt().toByteArray()) this.write(qq.toUInt().toByteArray())
} }
@Throws(IOException::class)
fun DataOutputStream.writeGroup(groupIdOrGroupNumber: Long) { fun DataOutputStream.writeGroup(groupIdOrGroupNumber: Long) {
this.write(groupIdOrGroupNumber.toUInt().toByteArray()) this.write(groupIdOrGroupNumber.toUInt().toByteArray())
} }
fun DataOutputStream.writeUByte(uByte: UByte) {
this.write(uByte.toInt())
}
fun DataOutputStream.writeLVByteArray(byteArray: ByteArray) { fun DataOutputStream.writeLVByteArray(byteArray: ByteArray) {
this.writeShort(byteArray.size) this.writeShort(byteArray.size)
this.write(byteArray) this.write(byteArray)
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
package net.mamoe.mirai.network.protocol.tim.packet package net.mamoe.mirai.network.protocol.tim.packet
import net.mamoe.mirai.message.defaults.MessageChain import net.mamoe.mirai.message.MessageChain
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.TIMProtocol
import net.mamoe.mirai.utils.dataDecode import net.mamoe.mirai.utils.dataDecode
import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.hexToBytes
......
package net.mamoe.mirai.network.protocol.tim.packet.action 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.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.dataEncode import net.mamoe.mirai.utils.dataEncode
......
package net.mamoe.mirai.network.protocol.tim.packet.action 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.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.dataEncode 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 @@ ...@@ -3,7 +3,7 @@
import jpcap.JpcapCaptor import jpcap.JpcapCaptor
import jpcap.packet.IPPacket import jpcap.packet.IPPacket
import jpcap.packet.UDPPacket 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.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.ServerEventPacket import net.mamoe.mirai.network.protocol.tim.packet.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
...@@ -151,7 +151,7 @@ object Main { ...@@ -151,7 +151,7 @@ object Main {
+ 1 + 1
) )
val chain = it.readMessageChain() val chain = it.readMessageChain()
println(chain.toObjectString()) println(chain)
} }
} }
......
This diff is collapsed.
...@@ -8,8 +8,8 @@ import net.mamoe.mirai.event.subscribeAll ...@@ -8,8 +8,8 @@ import net.mamoe.mirai.event.subscribeAll
import net.mamoe.mirai.event.subscribeAlways import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.event.subscribeUntilFalse import net.mamoe.mirai.event.subscribeUntilFalse
import net.mamoe.mirai.login import net.mamoe.mirai.login
import net.mamoe.mirai.message.defaults.Image import net.mamoe.mirai.message.Image
import net.mamoe.mirai.message.defaults.PlainText import net.mamoe.mirai.message.PlainText
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
import net.mamoe.mirai.utils.BotAccount import net.mamoe.mirai.utils.BotAccount
import net.mamoe.mirai.utils.Console import net.mamoe.mirai.utils.Console
...@@ -26,7 +26,7 @@ suspend fun main() { ...@@ -26,7 +26,7 @@ suspend fun main() {
), Console()) ), Console())
bot.login().let { bot.login().let {
if(it != LoginState.SUCCESS) { if (it != LoginState.SUCCESS) {
MiraiLogger.error("Login failed: " + it.name) MiraiLogger.error("Login failed: " + it.name)
exitProcess(0) exitProcess(0)
} }
...@@ -36,10 +36,10 @@ suspend fun main() { ...@@ -36,10 +36,10 @@ suspend fun main() {
//提供泛型以监听事件 //提供泛型以监听事件
subscribeAlways<FriendMessageEvent> { 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 { when {
it.message eq "你好" -> it.reply("你好!") it.message eq "你好" -> it.reply("你好!")
...@@ -78,7 +78,7 @@ suspend fun main() { ...@@ -78,7 +78,7 @@ suspend fun main() {
FriendMessageEvent::class.subscribeAll { FriendMessageEvent::class.subscribeAll {
always { 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