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>
<properties>
<kotlin.version>1.3.41</kotlin.version>
</properties>
<parent>
<groupId>net.mamoe</groupId>
<artifactId>mirai</artifactId>
@ -21,7 +17,6 @@
</parent>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core -->
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
@ -31,42 +26,36 @@
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
</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.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>
<build>
<resources>
<resource>
<directory>/src/main/resources</directory>
@ -94,63 +83,16 @@
<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>
</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>
<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>
</plugins>
</build>

View File

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

View File

@ -1,15 +1,26 @@
package net.mamoe.mirai.contact
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 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 {
val groupId = groupNumberToId(number)
val members = ContactList<QQ>()
override fun sendMessage(message: Message) {
override fun sendMessage(message: MessageChain) {
robot.network.messageHandler.sendGroupMessage(this, message)
}
@ -23,7 +34,7 @@ class Group(robot: Robot, number: Long) : Contact(robot, number), Closeable {
companion object {
@JvmStatic
fun groupNumberToId(number: Long): Long {
fun groupNumberToId(number: Long): Long {//求你别出错
val left: Long = number.toString().let {
if (it.length < 6) {
return@groupNumberToId number
@ -61,7 +72,7 @@ class Group(robot: Robot, number: Long) : Contact(robot, number), Closeable {
}
@JvmStatic
fun groupIdToNumber(id: Long): Long {
fun groupIdToNumber(id: Long): Long {//求你别出错
var left: Long = id.toString().let {
if (it.length < 6) {
return@groupIdToNumber id

View File

@ -3,18 +3,22 @@ package net.mamoe.mirai.contact
import net.mamoe.mirai.Robot
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.defaults.At
import net.mamoe.mirai.message.defaults.MessageChain
/**
* QQ 账号.
* 注意: 一个 [QQ] 实例并不是独立的, 它属于一个 [Robot].
*
* Java 获取 qq : `qq.getNumber()`
* Java 获取所属 robot: `qq.getRobot()`
*
* 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.
*
* @author Him188moe
*/
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)
}

View File

@ -12,19 +12,14 @@ import java.util.Objects;
*/
public final class FriendMessageEvent extends FriendEvent {
private final MessageChain messageChain;
private final String messageString;
public FriendMessageEvent(@NotNull Robot robot, @NotNull QQ sender, @NotNull MessageChain messageChain) {
super(robot, sender);
this.messageChain = Objects.requireNonNull(messageChain);
this.messageString = messageChain.toString();
}
public String getMessageString() {
return messageString;
}
public MessageChain getMessageChain() {
@NotNull
public MessageChain message() {
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.hookWhile
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.network.RobotNetworkHandler.*
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.ServerSendGroupMessageResponsePacket
import net.mamoe.mirai.network.packet.login.*
@ -85,16 +87,14 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
//private | internal
internal fun tryLogin(): CompletableFuture<LoginState> = this.tryLogin(200)//登录回复非常快, 没必要等太久.
/**
* 仅当 [LoginState] [LoginState.UNKNOWN] 且非 [LoginState.TIMEOUT] 才会调用 [loginHook].
* 如果要输入验证码, 那么会以参数 [LoginState.VERIFICATION_CODE] 调用 [loginHandler], 登录完成后再以 [LoginState.SUCCESS] 调用 [loginHandler]
*
* @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 future = CompletableFuture<LoginState>()
@ -199,10 +199,10 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
return this.loginFuture!!
}
@Synchronized
/**
* Not async
*/
@Synchronized
@ExperimentalUnsignedTypes
internal fun sendPacket(packet: ClientPacket) {
checkNotNull(socket) { "network closed" }
@ -329,7 +329,7 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
this.loginIP = packet.loginIP
this.loginTime = packet.loginTime
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 -> {
this.tgtgtKey = getRandomByteArray(16)
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 -> {
@ -419,7 +419,7 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
//登录成功后会收到大量上次的消息, 忽略掉
MiraiThreadPool.getInstance().schedule({
(packetHandlers[MessageHandler::class] as MessageHandler).ignoreMessage = false
messageHandler.ignoreMessage = false
}, 2, TimeUnit.SECONDS)
this.tlv0105 = packet.tlv0105
@ -432,7 +432,6 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
}
is ServerSKeyResponsePacket -> {
val actionHandler = packetHandlers[ActionHandler::class] as ActionHandler
actionHandler.sKey = packet.sKey
actionHandler.cookies = "uin=o" + robot.account.qqNumber + ";skey=" + actionHandler.sKey + ";"
@ -496,7 +495,12 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
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 -> {
@ -515,9 +519,8 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
}
}
fun sendFriendMessage(qq: QQ, message: Message) {
TODO()
//sendPacket(ClientSendFriendMessagePacket(robot.account.qqNumber, qq.number, sessionKey, message))
fun sendFriendMessage(qq: QQ, message: MessageChain) {
sendPacket(ClientSendFriendMessagePacket(robot.account.qqNumber, qq.number, sessionKey, message))
}
fun sendGroupMessage(group: Group, message: Message): Unit {
@ -532,7 +535,11 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
*/
inner class ActionHandler : PacketHandler() {
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
override fun onPacketReceived(packet: ServerPacket) {

View File

@ -230,7 +230,7 @@ fun DataOutputStream.writeZero(count: Int) {
@Throws(IOException::class)
fun DataOutputStream.writeRandom(length: Int) {
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)
fun DataOutputStream.writeGroup(groupIdOrGroupNumber: Long) {
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)
input.goto(93 + l1)
val l2 = input.readShort()
input.readNBytes(l2)//font
input.readVarByteArray()//font
input.skip(2)//2个0x00
message = input.readSections()
println(message.toDebugString())
println(message.toObjectString())
/*
val offset = unknownLength0 + fontLength//57
@ -218,14 +217,14 @@ class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray
val sectionLength = this.readShort().toLong()//sectionLength: short
this.skip(1)//message和face是 0x01, image是0x06
return when (messageType) {
0x01 -> PlainText(readShortVarString())
0x01 -> PlainText(readVarString())
0x02 -> {
//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
val id1 = FaceID.ofId(readShortVarNumber().toInt())//可能这个是id, 也可能下面那个
val id1 = FaceID.ofId(readVarNumber().toInt())//可能这个是id, 也可能下面那个
this.skip(this.readByte().toLong())
this.readShortVarNumber()//某id?
this.readVarNumber()//某id?
return Face(id1)
}
0x06 -> {

View File

@ -176,8 +176,12 @@ fun DataInputStream.readIP(): String {
return buff
}
fun DataInputStream.readShortVarString(): String {
return String(this.readNBytes(this.readShort().toInt()))
fun DataInputStream.readVarString(): String {
return String(this.readVarByteArray())
}
fun DataInputStream.readVarByteArray(): ByteArray {
return this.readNBytes(this.readShort().toInt())
}
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()) {
1 -> this.readByte()
2 -> this.readShort()

View File

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

View File

@ -29,6 +29,7 @@ object MiraiLogger {
this.print(e.cause.toString())*/
}
@Synchronized
private fun print(value: String?, color: LoggerTextFormat = LoggerTextFormat.WHITE) {
val s = SimpleDateFormat("MM-dd HH:mm:ss").format(Date())
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)
@Synchronized
private fun print(robot: Robot, value: String?, color: LoggerTextFormat = LoggerTextFormat.WHITE) {
val s = SimpleDateFormat("MM-dd HH:mm:ss").format(Date())
kotlin.io.println("$color[Mirai] $s #R${robot.id}: $value")
}
@Synchronized
private fun print(value: String?, color: LoggerTextFormat = LoggerTextFormat.WHITE) {
val s = SimpleDateFormat("MM-dd HH:mm:ss").format(Date())
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>
<module>mirai-core</module>
<module>mirai-native</module>
<module>mirai-ui</module>
</modules>
<repositories>
@ -27,41 +28,14 @@
<packaging>pom</packaging>
<properties>
<kotlin.version>1.3.41</kotlin.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</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>
<dependency>
@ -121,7 +95,114 @@
<version>0.5.2</version>
</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>
</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>