Enhanced Message

This commit is contained in:
Him188moe 2019-09-08 14:29:21 +08:00
parent 95b0d3b907
commit 6da2bd2f31
27 changed files with 666 additions and 439 deletions

View File

@ -9,10 +9,6 @@
<packaging>jar</packaging> <packaging>jar</packaging>
<properties>
<kotlin.version>1.3.41</kotlin.version>
</properties>
<parent> <parent>
<groupId>net.mamoe</groupId> <groupId>net.mamoe</groupId>
<artifactId>mirai</artifactId> <artifactId>mirai</artifactId>
@ -21,7 +17,6 @@
</parent> </parent>
<dependencies> <dependencies>
<!-- https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core -->
<dependency> <dependency>
<groupId>org.jetbrains.kotlinx</groupId> <groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId> <artifactId>kotlinx-coroutines-core</artifactId>
@ -31,42 +26,36 @@
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
<artifactId>netty-all</artifactId> <artifactId>netty-all</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.java.dev.jna</groupId> <groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId> <artifactId>jna</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jetbrains.kotlin</groupId> <groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId> <artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId> <artifactId>log4j-core</artifactId>
<version>2.12.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.yaml</groupId> <groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId> <artifactId>snakeyaml</artifactId>
<version>1.18</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.18</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jetbrains.kotlin</groupId> <groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId> <artifactId>kotlin-reflect</artifactId>
<version>1.3.41</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<resources> <resources>
<resource> <resource>
<directory>/src/main/resources</directory> <directory>/src/main/resources</directory>
@ -94,63 +83,16 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>shaded</shadedClassifierName>
</configuration>
</plugin> </plugin>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
</plugin>
<plugin> <plugin>
<groupId>org.jetbrains.kotlin</groupId> <groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId> <artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<jvmTarget>1.8</jvmTarget>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>

View File

@ -2,6 +2,7 @@ package net.mamoe.mirai.contact
import net.mamoe.mirai.Robot import net.mamoe.mirai.Robot
import net.mamoe.mirai.message.Message 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.PlainText
/** /**
@ -15,7 +16,14 @@ abstract class Contact(val robot: Robot, val number: Long) {
/** /**
* Async * Async
*/ */
abstract fun sendMessage(message: Message) abstract fun sendMessage(message: MessageChain)
fun sendMessage(message: Message) {
if (message is MessageChain) {
return sendMessage(message)
}
return sendMessage(message.toChain())
}
fun sendMessage(message: String) { fun sendMessage(message: String) {
this.sendMessage(PlainText(message)) this.sendMessage(PlainText(message))

View File

@ -1,15 +1,26 @@
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
import net.mamoe.mirai.Robot import net.mamoe.mirai.Robot
import net.mamoe.mirai.message.Message import net.mamoe.mirai.contact.Group.Companion.groupNumberToId
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.utils.ContactList import net.mamoe.mirai.utils.ContactList
import java.io.Closeable import java.io.Closeable
/**
*
*
* Java 获取 groupNumber: `group.getNumber()`
* Java 获取所属 robot: `group.getRobot()`
* Java 获取群成员列表: `group.getMembers()`
* Java 获取 groupId: `group.getGroupId()`
*
* Java 调用 [groupNumberToId] : `Group.groupNumberToId(number)`
*/
class Group(robot: Robot, number: Long) : Contact(robot, number), Closeable { class Group(robot: Robot, number: Long) : Contact(robot, number), Closeable {
val groupId = groupNumberToId(number) val groupId = groupNumberToId(number)
val members = ContactList<QQ>() val members = ContactList<QQ>()
override fun sendMessage(message: Message) { override fun sendMessage(message: MessageChain) {
robot.network.messageHandler.sendGroupMessage(this, message) robot.network.messageHandler.sendGroupMessage(this, message)
} }
@ -23,7 +34,7 @@ class Group(robot: Robot, number: Long) : Contact(robot, number), Closeable {
companion object { companion object {
@JvmStatic @JvmStatic
fun groupNumberToId(number: Long): Long { fun groupNumberToId(number: Long): Long {//求你别出错
val left: Long = number.toString().let { val left: Long = number.toString().let {
if (it.length < 6) { if (it.length < 6) {
return@groupNumberToId number return@groupNumberToId number
@ -61,7 +72,7 @@ class Group(robot: Robot, number: Long) : Contact(robot, number), Closeable {
} }
@JvmStatic @JvmStatic
fun groupIdToNumber(id: Long): Long { fun groupIdToNumber(id: Long): Long {//求你别出错
var left: Long = id.toString().let { var left: Long = id.toString().let {
if (it.length < 6) { if (it.length < 6) {
return@groupIdToNumber id return@groupIdToNumber id

View File

@ -3,18 +3,22 @@ package net.mamoe.mirai.contact
import net.mamoe.mirai.Robot import net.mamoe.mirai.Robot
import net.mamoe.mirai.message.Message import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.defaults.At import net.mamoe.mirai.message.defaults.At
import net.mamoe.mirai.message.defaults.MessageChain
/** /**
* QQ 账号. * QQ 账号.
* 注意: 一个 [QQ] 实例并不是独立的, 它属于一个 [Robot]. * 注意: 一个 [QQ] 实例并不是独立的, 它属于一个 [Robot].
* *
* Java 获取 qq : `qq.getNumber()`
* Java 获取所属 robot: `qq.getRobot()`
*
* A QQ instance helps you to receive message from or send message to. * A QQ instance helps you to receive message from or send message to.
* Notice that, one QQ instance belong to one [Robot], that is, QQ instances from different [Robot] are NOT the same. * Notice that, one QQ instance belong to one [Robot], that is, QQ instances from different [Robot] are NOT the same.
* *
* @author Him188moe * @author Him188moe
*/ */
class QQ(robot: Robot, number: Long) : Contact(robot, number) { class QQ(robot: Robot, number: Long) : Contact(robot, number) {
override fun sendMessage(message: Message) { override fun sendMessage(message: MessageChain) {
robot.network.messageHandler.sendFriendMessage(this, message) robot.network.messageHandler.sendFriendMessage(this, message)
} }

View File

@ -12,19 +12,14 @@ import java.util.Objects;
*/ */
public final class FriendMessageEvent extends FriendEvent { public final class FriendMessageEvent extends FriendEvent {
private final MessageChain messageChain; private final MessageChain messageChain;
private final String messageString;
public FriendMessageEvent(@NotNull Robot robot, @NotNull QQ sender, @NotNull MessageChain messageChain) { public FriendMessageEvent(@NotNull Robot robot, @NotNull QQ sender, @NotNull MessageChain messageChain) {
super(robot, sender); super(robot, sender);
this.messageChain = Objects.requireNonNull(messageChain); this.messageChain = Objects.requireNonNull(messageChain);
this.messageString = messageChain.toString();
} }
public String getMessageString() { @NotNull
return messageString; public MessageChain message() {
}
public MessageChain getMessageChain() {
return messageChain; return messageChain;
} }
} }

View File

@ -1,84 +0,0 @@
package net.mamoe.mirai.message;
import net.mamoe.mirai.contact.Contact;
import net.mamoe.mirai.contact.QQ;
import net.mamoe.mirai.message.defaults.At;
import net.mamoe.mirai.message.defaults.Image;
import net.mamoe.mirai.message.defaults.MessageChain;
import net.mamoe.mirai.message.defaults.PlainText;
import org.jetbrains.annotations.NotNull;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Objects;
/**
* 可发送的或从服务器接收的消息.
* 采用这样的消息模式是因为 QQ 的消息多元化, 一条消息中可包含 {@linkplain PlainText 纯文本}, {@linkplain Image 图片} .
*
* @author Him188moe
* @see Contact#sendMessage(Message) 发送这个消息
* @see MessageKt 若你使用 kotlin, 请查看针对 kotlin 的优化实现
*/
public abstract class Message {
@Override
public abstract String toString();
public String toDebugString() {
return this.getClass().getSimpleName() + String.format("(%s)", this.toString());
}
/**
* 把这个消息连接到另一个消息的头部. 相当于字符串相加
* <p>
* Connects this Message to the head of another Message.
* That is, another message becomes the tail of this message.
* This method does similar to {@link String#concat(String)}
* <p>
* E.g.:
* PlainText a = new PlainText("Hello ");
* PlainText b = new PlainText("world");
* PlainText c = a.concat(b);
* <p>
* the text of c is "Hello world"
*
* @param tail tail
* @return message connected
*/
public Message concat(@NotNull Message tail) {
return new MessageChain(this, Objects.requireNonNull(tail));
}
public final Message concat(String tail) {
return concat(new PlainText(tail));
}
public Message withImage(String imageId) {
// TODO: 2019/9/1
return this;
}
public Message withImage(BufferedImage image) {
// TODO: 2019/9/1
return this;
}
public Message withImage(File image) {
// TODO: 2019/9/1
return this;
}
public Message withAt(@NotNull QQ target) {
this.concat(target.at());
return this;
}
public Message withAt(int target) {
this.concat(new At(target));
return this;
}
}

View File

@ -0,0 +1,174 @@
package net.mamoe.mirai.message
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.defaults.At
import net.mamoe.mirai.message.defaults.Image
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.message.defaults.PlainText
import java.awt.image.BufferedImage
import java.io.File
import java.util.*
/**
* 可发送的或从服务器接收的消息.
* 采用这样的消息模式是因为 QQ 的消息多元化, 一条消息中可包含 [纯文本][PlainText], [图片][Image] .
*
* Kotlin, 使用 [Message] 与使用 [String] 几乎没有什么用法上的区别.
*
* @author Him188moe
* @see Contact.sendMessage
*/
abstract class Message {
internal abstract val type: Int
private var toStringCache: String? = null
private val cacheLock = object : Any() {}
internal abstract fun toStringImpl(): String
/**
* 得到用户层的文本消息. :
* - [PlainText] 得到 消息内容
* - [Image] 得到 "{ID}.png"
* - [At] 得到 "[@qq]"
*/
final override fun toString(): String {
synchronized(cacheLock) {
if (toStringCache != null) {
return toStringCache!!
}
this.toStringCache = toStringImpl()
return toStringCache!!
}
}
internal fun clearToStringCache() {
synchronized(cacheLock) {
toStringCache = null
}
}
/**
* 得到类似 "PlainText(内容)", "Image(ID)"
*/
open fun toObjectString(): String {
return this.javaClass.simpleName + String.format("(%s)", this.toString())
}
/**
* 转换为数据包使用的 byte array
*/
abstract fun toByteArray(): ByteArray
/**
* 比较两个 Message 的内容是否相等. :
* - [PlainText] 比较 [PlainText.text]
* - [Image] 比较 [Image.imageID]
*/
abstract infix fun valueEquals(another: Message): Boolean
/**
* 将这个消息的 [toString] [another] 比较
*/
infix fun valueEquals(another: String): Boolean = this.toString() == another
/**
* 把这个消息连接到另一个消息的头部. 相当于字符串相加
*
*
* 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): Message {
return MessageChain(this, Objects.requireNonNull(tail))
}
fun concat(tail: String): Message {
return concat(PlainText(tail))
}
fun withImage(imageId: String): Message {
// TODO: 2019/9/1
return this
}
fun withImage(image: BufferedImage): Message {
// TODO: 2019/9/1
return this
}
fun withImage(image: File): Message {
// TODO: 2019/9/1
return this
}
fun withAt(target: QQ): Message {
this.concat(target.at())
return this
}
fun withAt(target: Int): Message {
this.concat(At(target.toLong()))
return this
}
open fun toChain(): MessageChain {
return MessageChain(this)
}
/* For Kotlin */
/**
* 实现使用 '+' 操作符连接 [Message] [Message]
*/
infix operator fun plus(another: Message): Message = this.concat(another)
/**
* 实现使用 '+' 操作符连接 [Message] [String]
*/
infix operator fun plus(another: String): Message = this.concat(another)
/**
* 实现使用 '+' 操作符连接 [Message] [Number]
*/
infix operator fun plus(another: Number): Message = this.concat(another.toString())
/**
* 连接 [String] [Message]
*/
fun String.concat(another: Message): Message = 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
if (type != other.type) return false
return this.toString() == other.toString()
}
}

View File

@ -0,0 +1,22 @@
package net.mamoe.mirai.message
/**
* [Message] 在数据包中的 id([UByte])
*
* Java 调用方式:
* MessageId.at
*
* @author Him188moe
*/
object MessageId {
const val AT: Int = 0x00//todo 不知道是多少
const val FACE: Int = 0x00//todo 不知道是多少
const val TEXT: Int = 0x01
const val IMAGE: Int = 0x06
const val CHAIN: Int = 0xff//仅用于 equals. Packet 中不存在 Chain 概念
}

View File

@ -1,20 +0,0 @@
@file:JvmName("MessageKt")
package net.mamoe.mirai.message
import net.mamoe.mirai.message.defaults.PlainText
/**
* 实现使用 '+' 操作符连接 [Message] [Message]
*/
infix operator fun Message.plus(another: Message): Message = this.concat(another)
/**
* 实现使用 '+' 操作符连接 [Message] [String]
*/
infix operator fun Message.plus(another: String): Message = this.concat(another)
/**
* 连接 [String] [Message]
*/
infix fun String.concat(another: Message): Message = PlainText(this).concat(another)

View File

@ -1,34 +0,0 @@
package net.mamoe.mirai.message.defaults;
import net.mamoe.mirai.contact.QQ;
import net.mamoe.mirai.message.Message;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
/**
* At 一个人的消息.
*
* @author Him188moe
*/
public final class At extends Message {
private final long target;
public At(@NotNull QQ target) {
this(Objects.requireNonNull(target).getNumber());
}
public At(long target) {
this.target = target;
}
public long getTarget() {
return target;
}
@Override
public String toString() {
// TODO: 2019/9/4 At.toString
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,30 @@
package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageId
/**
* At 一个人
*
* @author Him188moe
*/
class At(val target: Long) : Message() {
override val type: Int = MessageId.AT
constructor(target: QQ) : this(target.number)
override fun toStringImpl(): String = "[@$target]"
override fun toByteArray(): ByteArray {
TODO()
}
override fun valueEquals(another: Message): Boolean {
if (another !is At) {
return false
}
return another.target == this.target
}
}

View File

@ -1,30 +0,0 @@
package net.mamoe.mirai.message.defaults;
import net.mamoe.mirai.message.FaceID;
import net.mamoe.mirai.message.Message;
/**
* QQ 自带表情
*
* @author Him188moe
*/
public final class Face extends Message {
private final FaceID id;
public Face(FaceID id) {
this.id = id;
}
public FaceID getId() {
return id;
}
@Override
public String toString() {
if (id == null) {
return "[face?]";
}
return String.format("[face%d]", id.getId());
}
}

View File

@ -0,0 +1,33 @@
package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.message.FaceID
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageId
/**
* QQ 自带表情
*
* @author Him188moe
*/
class Face(val id: FaceID?) : Message() {
override val type: Int = MessageId.FACE
override fun toStringImpl(): String {
return if (id == null) {
"[face?]"
} else String.format("[face%d]", id.id)
}
override fun toByteArray(): ByteArray {
TODO()
}
override fun valueEquals(another: Message): Boolean {
if (another !is Face) {
return false
}
return this.id == another.id
}
}

View File

@ -1,45 +0,0 @@
package net.mamoe.mirai.message.defaults;
import net.mamoe.mirai.message.Message;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
/**
* @author Him188moe
*/
public final class Image extends Message {
private String imageID;
public Image(InputStream inputStream) {
}
public Image(BufferedImage image) {
}
public Image(File imageFile) throws FileNotFoundException {
this(new FileInputStream(imageFile));
}
public Image(URL url) throws IOException {
this(ImageIO.read(url));
}
/**
* {xxxxx}.jpg
*
* @param imageID
*/
public Image(String imageID) {
this.imageID = imageID;
}
@Override
public String toString() {
return imageID;
}
}

View File

@ -0,0 +1,57 @@
package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageId
import java.awt.image.BufferedImage
import java.io.*
import java.net.URL
import javax.imageio.ImageIO
/**
* @author Him188moe
*/
class Image : Message {
override val type: Int = MessageId.IMAGE
private var imageID: String? = null
constructor(inputStream: InputStream) {
}
constructor(image: BufferedImage) {
}
@Throws(FileNotFoundException::class)
constructor(imageFile: File) : this(FileInputStream(imageFile)) {
}
@Throws(IOException::class)
constructor(url: URL) : this(ImageIO.read(url)) {
}
/**
* {xxxxx}.jpg
*
* @param imageID
*/
constructor(imageID: String) {
this.imageID = imageID
}
override fun toStringImpl(): String {
return imageID!!
}
override fun toByteArray(): ByteArray {
TODO()
}
override fun valueEquals(another: Message): Boolean {
if (another !is Image) {
return false
}
return this.imageID == another.imageID
}
}

View File

@ -1,61 +0,0 @@
package net.mamoe.mirai.message.defaults;
import net.mamoe.mirai.message.Message;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author Him188moe
*/
public final class MessageChain extends Message {
private LinkedList<Message> list = new LinkedList<>();
public MessageChain(@NotNull Message head, @NotNull Message tail) {
Objects.requireNonNull(head);
Objects.requireNonNull(tail);
list.add(head);
list.add(tail);
}
public MessageChain(@NotNull Message message) {
Objects.requireNonNull(message);
list.add(message);
}
public MessageChain() {
}
/**
* @return An unmodifiable list
*/
public List<Message> toList() {
return List.copyOf(list);
}
public Stream<Message> stream() {
return new ArrayList<>(list).stream();
}
@Override
public synchronized String toString() {
return this.list.stream().map(Message::toString).collect(Collectors.joining(""));
}
public synchronized String toDebugString() {
return String.format("MessageChain(%s)", this.list.stream().map(Message::toDebugString).collect(Collectors.joining(", ")));
}
@Override
public synchronized Message concat(@NotNull Message tail) {
this.list.add(tail);
return this;
}
}

View File

@ -0,0 +1,71 @@
package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageId
import net.mamoe.mirai.utils.lazyEncode
import java.util.*
import java.util.stream.Collectors
import java.util.stream.Stream
class MessageChain : Message {
override val type: Int = MessageId.CHAIN
internal val list = 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()
fun toList(): List<Message> {
return list.toList()
}
fun stream(): Stream<Message> {
return ArrayList(list).stream()
}
@Synchronized
override fun toStringImpl(): String {
return this.list.stream().map { it.toString() }.collect(Collectors.joining(""))
}
@Synchronized
override fun toObjectString(): String {
return String.format("MessageChain(%s)", this.list.stream().map { it.toObjectString() }.collect(Collectors.joining(", ")))
}
@Synchronized
override fun concat(tail: Message): Message {
this.list.add(tail)
clearToStringCache()
return this
}
override fun toChain(): MessageChain {
return this
}
override fun toByteArray(): ByteArray = lazyEncode {
stream().forEach { message ->
it.write(message.toByteArray())
}
}
override fun valueEquals(another: Message): Boolean {
if (another !is MessageChain) {
return false
}
return this.list == another.list
}
}

View File

@ -1,19 +0,0 @@
package net.mamoe.mirai.message.defaults;
import net.mamoe.mirai.message.Message;
/**
* @author Him188moe
*/
public final class PlainText extends Message {
private final String text;
public PlainText(String text) {
this.text = text;
}
@Override
public String toString() {
return text;
}
}

View File

@ -0,0 +1,34 @@
package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageId
import net.mamoe.mirai.network.packet.writeVarByteArray
import net.mamoe.mirai.network.packet.writeVarString
import net.mamoe.mirai.utils.lazyEncode
/**
* @author Him188moe
*/
class PlainText(private val text: String) : Message() {
override val type: Int = MessageId.TEXT
override fun toStringImpl(): String {
return text
}
override fun toByteArray(): ByteArray = lazyEncode { section ->
section.writeByte(this.type)
section.writeVarByteArray(lazyEncode { child ->
child.writeByte(0x01)
child.writeVarString(this.text)
})
}
override fun valueEquals(another: Message): Boolean {
if (another !is PlainText) {
return false
}
return this.text == another.text
}
}

View File

@ -14,8 +14,10 @@ import net.mamoe.mirai.event.events.qq.FriendMessageEvent
import net.mamoe.mirai.event.events.robot.RobotLoginSucceedEvent import net.mamoe.mirai.event.events.robot.RobotLoginSucceedEvent
import net.mamoe.mirai.event.hookWhile import net.mamoe.mirai.event.hookWhile
import net.mamoe.mirai.message.Message import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.network.RobotNetworkHandler.* import net.mamoe.mirai.network.RobotNetworkHandler.*
import net.mamoe.mirai.network.packet.* import net.mamoe.mirai.network.packet.*
import net.mamoe.mirai.network.packet.action.ClientSendFriendMessagePacket
import net.mamoe.mirai.network.packet.action.ServerSendFriendMessageResponsePacket import net.mamoe.mirai.network.packet.action.ServerSendFriendMessageResponsePacket
import net.mamoe.mirai.network.packet.action.ServerSendGroupMessageResponsePacket import net.mamoe.mirai.network.packet.action.ServerSendGroupMessageResponsePacket
import net.mamoe.mirai.network.packet.login.* import net.mamoe.mirai.network.packet.login.*
@ -85,16 +87,14 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
//private | internal //private | internal
internal fun tryLogin(): CompletableFuture<LoginState> = this.tryLogin(200)//登录回复非常快, 没必要等太久.
/** /**
* 仅当 [LoginState] [LoginState.UNKNOWN] 且非 [LoginState.TIMEOUT] 才会调用 [loginHook]. * 仅当 [LoginState] [LoginState.UNKNOWN] 且非 [LoginState.TIMEOUT] 才会调用 [loginHook].
* 如果要输入验证码, 那么会以参数 [LoginState.VERIFICATION_CODE] 调用 [loginHandler], 登录完成后再以 [LoginState.SUCCESS] 调用 [loginHandler] * 如果要输入验证码, 那么会以参数 [LoginState.VERIFICATION_CODE] 调用 [loginHandler], 登录完成后再以 [LoginState.SUCCESS] 调用 [loginHandler]
* *
* @param touchingTimeoutMillis 连接每个服务器的 timeout * @param touchingTimeoutMillis 连接每个服务器的 timeout
*/ */
internal fun tryLogin(touchingTimeoutMillis: Long): CompletableFuture<LoginState> { @JvmOverloads
internal fun tryLogin(touchingTimeoutMillis: Long = 200): CompletableFuture<LoginState> {
val ipQueue: LinkedList<String> = LinkedList(Protocol.SERVER_IP) val ipQueue: LinkedList<String> = LinkedList(Protocol.SERVER_IP)
val future = CompletableFuture<LoginState>() val future = CompletableFuture<LoginState>()
@ -199,10 +199,10 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
return this.loginFuture!! return this.loginFuture!!
} }
@Synchronized
/** /**
* Not async * Not async
*/ */
@Synchronized
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes
internal fun sendPacket(packet: ClientPacket) { internal fun sendPacket(packet: ClientPacket) {
checkNotNull(socket) { "network closed" } checkNotNull(socket) { "network closed" }
@ -329,7 +329,7 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
this.loginIP = packet.loginIP this.loginIP = packet.loginIP
this.loginTime = packet.loginTime this.loginTime = packet.loginTime
this.token0825 = packet.token0825 this.token0825 = packet.token0825
sendPacket(ClientPasswordSubmissionPacket(robot.account.qqNumber, robot.account.password, packet.loginTime, packet.loginIP, this.tgtgtKey!!, packet.token0825)) sendPacket(ClientPasswordSubmissionPacket(robot.account.qqNumber, robot.account.password, packet.loginTime, packet.loginIP, this.tgtgtKey, packet.token0825))
} }
} }
@ -341,7 +341,7 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
is ServerVerificationCodeCorrectPacket -> { is ServerVerificationCodeCorrectPacket -> {
this.tgtgtKey = getRandomByteArray(16) this.tgtgtKey = getRandomByteArray(16)
this.token00BA = packet.token00BA this.token00BA = packet.token00BA
sendPacket(ClientLoginResendPacket3105(robot.account.qqNumber, robot.account.password, this.loginTime, this.loginIP, this.tgtgtKey!!, this.token0825, this.token00BA)) sendPacket(ClientLoginResendPacket3105(robot.account.qqNumber, robot.account.password, this.loginTime, this.loginIP, this.tgtgtKey, this.token0825, this.token00BA))
} }
is ServerLoginResponseVerificationCodeInitPacket -> { is ServerLoginResponseVerificationCodeInitPacket -> {
@ -419,7 +419,7 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
//登录成功后会收到大量上次的消息, 忽略掉 //登录成功后会收到大量上次的消息, 忽略掉
MiraiThreadPool.getInstance().schedule({ MiraiThreadPool.getInstance().schedule({
(packetHandlers[MessageHandler::class] as MessageHandler).ignoreMessage = false messageHandler.ignoreMessage = false
}, 2, TimeUnit.SECONDS) }, 2, TimeUnit.SECONDS)
this.tlv0105 = packet.tlv0105 this.tlv0105 = packet.tlv0105
@ -432,7 +432,6 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
} }
is ServerSKeyResponsePacket -> { is ServerSKeyResponsePacket -> {
val actionHandler = packetHandlers[ActionHandler::class] as ActionHandler
actionHandler.sKey = packet.sKey actionHandler.sKey = packet.sKey
actionHandler.cookies = "uin=o" + robot.account.qqNumber + ";skey=" + actionHandler.sKey + ";" actionHandler.cookies = "uin=o" + robot.account.qqNumber + ";skey=" + actionHandler.sKey + ";"
@ -496,7 +495,12 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
return return
} }
FriendMessageEvent(robot, robot.contacts.getQQ(packet.qq), packet.message) val friendMessageEvent = FriendMessageEvent(robot, robot.contacts.getQQ(packet.qq), packet.message)
friendMessageEvent.broadcast()
if (friendMessageEvent.message() valueEquals "你好") {
friendMessageEvent.qq.sendMessage("你好")
}
} }
is ServerGroupMessageEventPacket -> { is ServerGroupMessageEventPacket -> {
@ -515,9 +519,8 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
} }
} }
fun sendFriendMessage(qq: QQ, message: Message) { fun sendFriendMessage(qq: QQ, message: MessageChain) {
TODO() sendPacket(ClientSendFriendMessagePacket(robot.account.qqNumber, qq.number, sessionKey, message))
//sendPacket(ClientSendFriendMessagePacket(robot.account.qqNumber, qq.number, sessionKey, message))
} }
fun sendGroupMessage(group: Group, message: Message): Unit { fun sendGroupMessage(group: Group, message: Message): Unit {
@ -532,7 +535,11 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
*/ */
inner class ActionHandler : PacketHandler() { inner class ActionHandler : PacketHandler() {
internal lateinit var cookies: String internal lateinit var cookies: String
internal lateinit var sKey: String internal var sKey: String = ""
set(value) {
field = value
gtk = getGTK(value)
}
internal var gtk: Int = 0 internal var gtk: Int = 0
override fun onPacketReceived(packet: ServerPacket) { override fun onPacketReceived(packet: ServerPacket) {

View File

@ -230,7 +230,7 @@ fun DataOutputStream.writeZero(count: Int) {
@Throws(IOException::class) @Throws(IOException::class)
fun DataOutputStream.writeRandom(length: Int) { fun DataOutputStream.writeRandom(length: Int) {
repeat(length) { repeat(length) {
this.writeByte((Math.random() * 255).toInt().toByte().toInt()) this.writeByte((Math.random() * 255).toInt())
} }
} }
@ -244,4 +244,13 @@ fun DataOutputStream.writeQQ(qq: Long) {
@Throws(IOException::class) @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.writeVarByteArray(byteArray: ByteArray) {
this.writeShort(byteArray.size)
this.write(byteArray)
}
fun DataOutputStream.writeVarString(str: String) {
this.writeVarByteArray(str.toByteArray())
} }

View File

@ -199,11 +199,10 @@ class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray
val l1 = input.readShortAt(22) val l1 = input.readShortAt(22)
input.goto(93 + l1) input.goto(93 + l1)
val l2 = input.readShort() input.readVarByteArray()//font
input.readNBytes(l2)//font
input.skip(2)//2个0x00 input.skip(2)//2个0x00
message = input.readSections() message = input.readSections()
println(message.toDebugString()) println(message.toObjectString())
/* /*
val offset = unknownLength0 + fontLength//57 val offset = unknownLength0 + fontLength//57
@ -218,14 +217,14 @@ class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray
val sectionLength = this.readShort().toLong()//sectionLength: short val sectionLength = this.readShort().toLong()//sectionLength: short
this.skip(1)//message和face是 0x01, image是0x06 this.skip(1)//message和face是 0x01, image是0x06
return when (messageType) { return when (messageType) {
0x01 -> PlainText(readShortVarString()) 0x01 -> PlainText(readVarString())
0x02 -> { 0x02 -> {
//00 01 AF 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 F0 //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 //00 01 0C 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 4D
val id1 = FaceID.ofId(readShortVarNumber().toInt())//可能这个是id, 也可能下面那个 val id1 = FaceID.ofId(readVarNumber().toInt())//可能这个是id, 也可能下面那个
this.skip(this.readByte().toLong()) this.skip(this.readByte().toLong())
this.readShortVarNumber()//某id? this.readVarNumber()//某id?
return Face(id1) return Face(id1)
} }
0x06 -> { 0x06 -> {

View File

@ -176,8 +176,12 @@ fun DataInputStream.readIP(): String {
return buff return buff
} }
fun DataInputStream.readShortVarString(): String { fun DataInputStream.readVarString(): String {
return String(this.readNBytes(this.readShort().toInt())) return String(this.readVarByteArray())
}
fun DataInputStream.readVarByteArray(): ByteArray {
return this.readNBytes(this.readShort().toInt())
} }
fun DataInputStream.readString(length: Int): String { fun DataInputStream.readString(length: Int): String {
@ -206,7 +210,7 @@ fun <N : Number> DataInputStream.readNBytes(length: N): ByteArray {
} }
fun DataInputStream.readShortVarNumber(): Number { fun DataInputStream.readVarNumber(): Number {
return when (this.readShort().toInt()) { return when (this.readShort().toInt()) {
1 -> this.readByte() 1 -> this.readByte()
2 -> this.readShort() 2 -> this.readShort()

View File

@ -1,5 +1,6 @@
package net.mamoe.mirai.network.packet.action package net.mamoe.mirai.network.packet.action
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.network.Protocol import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.network.packet.* import net.mamoe.mirai.network.packet.*
import net.mamoe.mirai.utils.lazyEncode import net.mamoe.mirai.utils.lazyEncode
@ -14,7 +15,7 @@ class ClientSendFriendMessagePacket(
private val robotQQ: Long, private val robotQQ: Long,
private val targetQQ: Long, private val targetQQ: Long,
private val sessionKey: ByteArray, private val sessionKey: ByteArray,
private val message: String private val message: MessageChain
) : ClientPacket() { ) : ClientPacket() {
override fun encode() { override fun encode() {
this.writeRandom(2)//part of packet id this.writeRandom(2)//part of packet id
@ -36,24 +37,20 @@ class ClientSendFriendMessagePacket(
it.writeTime() it.writeTime()
it.writeRandom(4) it.writeRandom(4)
it.writeHex("00 00 00 00 09 00 86") it.writeHex("00 00 00 00 09 00 86")
it.writeHex(Protocol.friendMessageConst1) it.writeHex(Protocol.friendMessageConst1)//... 85 E9 BB 91
it.writeZero(2) it.writeZero(2)
if ("[face" in message
|| ".gif]" in message it.write(message.toByteArray())
|| ".jpg]" in message
|| ".png]" in message /*
) {
TODO("复合消息构建")
} else {
//Plain text //Plain text
val bytes = message.toByteArray() val bytes = message.toByteArray()
it.writeByte(0x01) it.writeByte(0x01)
it.writeShort(bytes.size + 3) it.writeShort(bytes.size + 3)
it.writeByte(0x01) it.writeByte(0x01)
it.writeShort(bytes.size) it.writeShort(bytes.size)
it.write(bytes) it.write(bytes)*/
}
} }
} }
} }

View File

@ -29,6 +29,7 @@ object MiraiLogger {
this.print(e.cause.toString())*/ this.print(e.cause.toString())*/
} }
@Synchronized
private fun print(value: String?, color: LoggerTextFormat = LoggerTextFormat.WHITE) { private fun print(value: String?, color: LoggerTextFormat = LoggerTextFormat.WHITE) {
val s = SimpleDateFormat("MM-dd HH:mm:ss").format(Date()) val s = SimpleDateFormat("MM-dd HH:mm:ss").format(Date())
kotlin.io.println("$color[Mirai] $s : $value") kotlin.io.println("$color[Mirai] $s : $value")
@ -52,12 +53,14 @@ infix fun Robot.success(o: Any?) = print(this, o.toString(), LoggerTextFormat.GR
infix fun Robot.debug(o: Any?) = print(this, o.toString(), LoggerTextFormat.YELLOW) infix fun Robot.debug(o: Any?) = print(this, o.toString(), LoggerTextFormat.YELLOW)
@Synchronized
private fun print(robot: Robot, value: String?, color: LoggerTextFormat = LoggerTextFormat.WHITE) { private fun print(robot: Robot, value: String?, color: LoggerTextFormat = LoggerTextFormat.WHITE) {
val s = SimpleDateFormat("MM-dd HH:mm:ss").format(Date()) val s = SimpleDateFormat("MM-dd HH:mm:ss").format(Date())
kotlin.io.println("$color[Mirai] $s #R${robot.id}: $value") kotlin.io.println("$color[Mirai] $s #R${robot.id}: $value")
} }
@Synchronized
private fun print(value: String?, color: LoggerTextFormat = LoggerTextFormat.WHITE) { private fun print(value: String?, color: LoggerTextFormat = LoggerTextFormat.WHITE) {
val s = SimpleDateFormat("MM-dd HH:mm:ss").format(Date()) val s = SimpleDateFormat("MM-dd HH:mm:ss").format(Date())
kotlin.io.println("$color[Mirai] $s : $value") kotlin.io.println("$color[Mirai] $s : $value")

39
mirai-ui/pom.xml Normal file
View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>mirai-ui</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<parent>
<groupId>net.mamoe</groupId>
<artifactId>mirai</artifactId>
<version>1.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<dependencies>
</dependencies>
<build>
<resources>
<resource>
<directory>/src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
<plugins>
</plugins>
</build>
</project>

139
pom.xml
View File

@ -12,6 +12,7 @@
<modules> <modules>
<module>mirai-core</module> <module>mirai-core</module>
<module>mirai-native</module> <module>mirai-native</module>
<module>mirai-ui</module>
</modules> </modules>
<repositories> <repositories>
@ -27,41 +28,14 @@
<packaging>pom</packaging> <packaging>pom</packaging>
<properties> <properties>
<kotlin.version>1.3.41</kotlin.version>
<maven.compiler.source>11</maven.compiler.source> <maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target> <maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<build>
<defaultGoal>package</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>7.1</version>
</dependency>
</dependencies>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.6.0</version>
</plugin>
</plugins>
</build>
<dependencies> <dependencies>
<dependency> <dependency>
@ -121,7 +95,114 @@
<version>0.5.2</version> <version>0.5.2</version>
</dependency> </dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.1</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.18</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>1.3.41</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
<build>
<defaultGoal>package</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>7.1</version>
</dependency>
</dependencies>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.6.0</version>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>shaded</shadedClassifierName>
</configuration>
</plugin>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.6.0</version>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<jvmTarget>1.8</jvmTarget>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project> </project>