diff --git a/mirai-api/src/main/java/net/mamoe/mirai/Bot.java b/mirai-api/src/main/java/net/mamoe/mirai/Bot.java new file mode 100644 index 000000000..0c60309dc --- /dev/null +++ b/mirai-api/src/main/java/net/mamoe/mirai/Bot.java @@ -0,0 +1,15 @@ +package net.mamoe.mirai; + +import net.mamoe.mirai.utils.BotAccount; + +import java.io.Closeable; + +/** + * @author Him188moe + */ +public interface Bot extends Closeable { + + BotAccount getAccount(); + + // TODO: 2019/9/13 add more +} diff --git a/mirai-core/src/main/java/net/mamoe/mirai/utils/BotAccount.java b/mirai-api/src/main/java/net/mamoe/mirai/utils/BotAccount.java similarity index 100% rename from mirai-core/src/main/java/net/mamoe/mirai/utils/BotAccount.java rename to mirai-api/src/main/java/net/mamoe/mirai/utils/BotAccount.java diff --git a/mirai-core/pom.xml b/mirai-core/pom.xml index a2a0d6252..bff6e2c4d 100644 --- a/mirai-core/pom.xml +++ b/mirai-core/pom.xml @@ -17,6 +17,31 @@ + + org.pcap4j + pcap4j-core + 1.8.2 + compile + + + org.pcap4j + pcap4j-packetfactory-static + 1.8.2 + compile + + + + net.mamoe + mirai-api + 1.0 + compile + + + + com.google.protobuf + protobuf-java + + org.jetbrains.kotlinx kotlinx-coroutines-core @@ -56,7 +81,12 @@ org.jsoup jsoup - 1.12.1 + compile + + + + org.ini4j + ini4j compile diff --git a/mirai-core/src/main/java/net/mamoe/mirai/Bot.java b/mirai-core/src/main/java/net/mamoe/mirai/Bot.java index dc0f9ee90..beda974d4 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/Bot.java +++ b/mirai-core/src/main/java/net/mamoe/mirai/Bot.java @@ -4,6 +4,7 @@ import lombok.Getter; import net.mamoe.mirai.contact.Group; import net.mamoe.mirai.contact.QQ; import net.mamoe.mirai.network.BotNetworkHandler; +import net.mamoe.mirai.network.BotNetworkHandlerImpl; import net.mamoe.mirai.utils.BotAccount; import net.mamoe.mirai.utils.ContactList; import net.mamoe.mirai.utils.config.MiraiConfigSection; @@ -19,7 +20,7 @@ import java.util.concurrent.atomic.AtomicInteger; *
* {@link Bot} 由 3 个模块组成. * {@linkplain ContactSystem 联系人管理}: 可通过 {@link Bot#contacts} 访问 - * {@linkplain BotNetworkHandler 网络处理器}: 可通过 {@link Bot#network} 访问 + * {@linkplain BotNetworkHandlerImpl 网络处理器}: 可通过 {@link Bot#network} 访问 * {@linkplain BotAccount 机器人账号信息}: 可通过 {@link Bot#account} 访问 *
* 若你需要得到机器人的 QQ 账号, 请访问 {@link Bot#account} @@ -29,7 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger; * Bot that is the base of the whole program. * It consists of * a {@link ContactSystem}, which manage contacts such as {@link QQ} and {@link Group}; - * a {@link BotNetworkHandler}, which manages the connection to the server; + * a {@link BotNetworkHandlerImpl}, which manages the connection to the server; * a {@link BotAccount}, which stores the account information(e.g. qq number the bot) *
* To get all the QQ contacts, access {@link Bot#account} @@ -118,7 +119,7 @@ public final class Bot implements Closeable { Objects.requireNonNull(owners); this.account = account; this.owners = Collections.unmodifiableList(owners); - this.network = new BotNetworkHandler(this); + this.network = new BotNetworkHandlerImpl(this); } diff --git a/mirai-core/src/main/java/net/mamoe/mirai/MiraiServer.java b/mirai-core/src/main/java/net/mamoe/mirai/MiraiServer.java index a93fba56d..b722713c2 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/MiraiServer.java +++ b/mirai-core/src/main/java/net/mamoe/mirai/MiraiServer.java @@ -212,22 +212,29 @@ public final class MiraiServer { } - String qqList = "3150499752----1234567890\n" + - "3119292829----987654321\n" + - "2399148773----12345678910\n" + - "3145561616----987654321\n" + - "2374150554----12345678910\n" + - "2375307394----12345678910\n" + - "2401645747----12345678910\n" + - "1515419818----1234567890\n" + - "3107367848----987654321\n"; + String qqList = + "2573990098----qq123456789\n" + + "3303923865----q123456789\n" + + "3349933294----q123456789\n" + + "3303708824----q123456789\n" + + "3227036647----q123456789\n" + + "3451394431----q123456789\n" + + "3533243484----q123456789\n" + + "3364512686----q123456789\n" + + "3137567463----q123456789\n" + + "3414786399----q123456789\n" + + "3347405939----q123456789\n" + + "3544089622----q123456789\n" + + "3108512993----q123456789\n" + + "2985563549----q123456789\n" + + "3463531892----q123456789\n"; private Bot getAvailableBot() throws ExecutionException, InterruptedException { for (String it : qqList.split("\n")) { var strings = it.split("----"); var bot = new Bot(new BotAccount(Long.parseLong(strings[0]), strings[1]), List.of()); - if (bot.network.tryLogin$mirai_core().get() == LoginState.SUCCESS) { + if (bot.network.tryLogin(200).get() == LoginState.SUCCESS) { MiraiLoggerKt.success(bot, "Login succeed"); return bot; } diff --git a/mirai-core/src/main/java/net/mamoe/mirai/contact/Contact.kt b/mirai-core/src/main/java/net/mamoe/mirai/contact/Contact.kt index e45748d89..f26f7cfc5 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/contact/Contact.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/contact/Contact.kt @@ -4,6 +4,9 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.message.Message import net.mamoe.mirai.message.defaults.MessageChain import net.mamoe.mirai.message.defaults.PlainText +import net.mamoe.mirai.message.defaults.UnsolvedImage +import net.mamoe.mirai.network.LoginSession +import java.util.concurrent.CompletableFuture /** * 联系人. @@ -20,6 +23,13 @@ abstract class Contact internal constructor(val bot: Bot, val number: Long) { */ abstract fun sendMessage(message: MessageChain) + /** + * 上传图片 + */ + fun uploadImage(session: LoginSession, image: UnsolvedImage): CompletableFuture { + return image.upload(session, this) + } + fun sendMessage(message: Message) { if (message is MessageChain) { return sendMessage(message) diff --git a/mirai-core/src/main/java/net/mamoe/mirai/contact/Group.kt b/mirai-core/src/main/java/net/mamoe/mirai/contact/Group.kt index 3bd32fe02..deea82b8c 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/contact/Group.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/contact/Group.kt @@ -26,7 +26,7 @@ class Group(bot: Bot, number: Long) : Contact(bot, number), Closeable { val members = ContactList() override fun sendMessage(message: MessageChain) { - bot.network.messageHandler.sendGroupMessage(this, message) + bot.network.message.sendGroupMessage(this, message) } override fun sendXMLMessage(message: String) { diff --git a/mirai-core/src/main/java/net/mamoe/mirai/contact/QQ.kt b/mirai-core/src/main/java/net/mamoe/mirai/contact/QQ.kt index 39d58bccc..0fd755d0f 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/contact/QQ.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/contact/QQ.kt @@ -12,14 +12,14 @@ import net.mamoe.mirai.message.defaults.MessageChain * Java 获取 qq 号: `qq.getNumber()` * Java 获取所属 bot: `qq.getBot()` * - * A QQ instance helps you to receive message from or send message to. + * A QQ instance helps you to receive message from or sendPacket message to. * Notice that, one QQ instance belong to one [Bot], that is, QQ instances from different [Bot] are NOT the same. * * @author Him188moe */ class QQ(bot: Bot, number: Long) : Contact(bot, number) { override fun sendMessage(message: MessageChain) { - bot.network.messageHandler.sendFriendMessage(this, message) + bot.network.message.sendFriendMessage(this, message) } override fun sendXMLMessage(message: String) { diff --git a/mirai-core/src/main/java/net/mamoe/mirai/message/FaceID.java b/mirai-core/src/main/java/net/mamoe/mirai/message/FaceID.java index 3a8831860..2a8c11952 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/message/FaceID.java +++ b/mirai-core/src/main/java/net/mamoe/mirai/message/FaceID.java @@ -4,6 +4,8 @@ package net.mamoe.mirai.message; * @author Him188moe */ public enum FaceID { + unknown(0xff), + Face_jingya(0), Face_piezui(1), Face_se(2), diff --git a/mirai-core/src/main/java/net/mamoe/mirai/message/Message.kt b/mirai-core/src/main/java/net/mamoe/mirai/message/Message.kt index ddba4dec2..a44184591 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/message/Message.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/message/Message.kt @@ -2,10 +2,7 @@ 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 net.mamoe.mirai.message.defaults.* import java.awt.image.BufferedImage import java.io.File import java.util.* @@ -106,41 +103,22 @@ abstract class Message { * @param tail tail * @return message connected */ - open fun concat(tail: Message): Message { + open fun concat(tail: Message): MessageChain { return MessageChain(this, Objects.requireNonNull(tail)) } - fun concat(tail: String): Message { + fun concat(tail: String): MessageChain { return concat(PlainText(tail)) } - fun withImage(imageId: String): Message { + 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) - // TODO: 2019/9/1 - return this - } + infix fun withAt(target: QQ): MessageChain = this + target.at() + infix fun withAt(target: Long): MessageChain = this + At(target) - 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) @@ -152,22 +130,22 @@ abstract class Message { /** * 实现使用 '+' 操作符连接 [Message] 与 [Message] */ - infix operator fun plus(another: Message): Message = this.concat(another) + infix operator fun plus(another: Message): MessageChain = this.concat(another) /** * 实现使用 '+' 操作符连接 [Message] 与 [String] */ - infix operator fun plus(another: String): Message = this.concat(another) + infix operator fun plus(another: String): MessageChain = this.concat(another) /** * 实现使用 '+' 操作符连接 [Message] 与 [Number] */ - infix operator fun plus(another: Number): Message = this.concat(another.toString()) + infix operator fun plus(another: Number): MessageChain = this.concat(another.toString()) /** * 连接 [String] 与 [Message] */ - fun String.concat(another: Message): Message = PlainText(this).concat(another) + fun String.concat(another: Message): MessageChain = PlainText(this).concat(another) override fun hashCode(): Int { return javaClass.hashCode() diff --git a/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/Face.kt b/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/Face.kt index 56a5fba4d..c22d31fe9 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/Face.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/Face.kt @@ -3,26 +3,38 @@ package net.mamoe.mirai.message.defaults import net.mamoe.mirai.message.FaceID import net.mamoe.mirai.message.Message import net.mamoe.mirai.message.MessageId +import net.mamoe.mirai.network.packet.writeHex +import net.mamoe.mirai.network.packet.writeLVByteArray +import net.mamoe.mirai.utils.lazyEncode /** * QQ 自带表情 * * @author Him188moe */ -class Face(val id: FaceID?) : Message() { +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) + return String.format("[face%d]", id.id) } - override fun toByteArray(): ByteArray { - TODO() + override fun toByteArray(): ByteArray = lazyEncode { section -> + section.writeByte(this.type) + + section.writeLVByteArray(lazyEncode { 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 valueEquals(another: Message): Boolean { if (another !is Face) { return false diff --git a/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/Image.kt b/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/Image.kt index 08b80853e..6204f9c82 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/Image.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/Image.kt @@ -2,66 +2,49 @@ package net.mamoe.mirai.message.defaults import net.mamoe.mirai.message.Message import net.mamoe.mirai.message.MessageId -import net.mamoe.mirai.network.packet.cutTail -import net.mamoe.mirai.network.packet.md5 -import java.awt.image.BufferedImage -import java.io.* -import java.net.URL -import javax.imageio.ImageIO +import net.mamoe.mirai.network.packet.writeHex +import net.mamoe.mirai.network.packet.writeLVByteArray +import net.mamoe.mirai.network.packet.writeLVString +import net.mamoe.mirai.utils.lazyEncode /** + * 图片消息. + * 由接收消息时构建, 可直接发送 + * * @author Him188moe */ -class Image : Message { +open class Image internal constructor(val imageID: String) : 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!! + return imageID } - override fun toByteArray(): ByteArray { - TODO() + override fun toByteArray(): ByteArray = lazyEncode { section -> + section.writeByte(0x03)//todo 可能是 0x03? + + section.writeLVByteArray(lazyEncode { 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 valueEquals(another: Message): Boolean { - if (another !is Image) { - return false + if (another is Image) { + return this.imageID == another.imageID } - return this.imageID == another.imageID - } - companion object { - fun getImageID(filename: String): ByteArray = md5(filename).cutTail(1) + return false } -} - -fun main() { - println(0xB0) } \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/MessageChain.kt b/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/MessageChain.kt index 1273784a9..3f6696db3 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/MessageChain.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/MessageChain.kt @@ -11,7 +11,7 @@ import java.util.stream.Stream class MessageChain : Message { override val type: Int = MessageId.CHAIN - internal val list = LinkedList() + val list: MutableList = Collections.synchronizedList(LinkedList()) constructor(head: Message, tail: Message) { Objects.requireNonNull(head) @@ -26,17 +26,16 @@ class MessageChain : Message { list.add(message) } - constructor() - - fun toList(): List { - return list.toList() + constructor(messages: Collection) { + list.addAll(messages) } + constructor() + fun size(): Int { return list.size } - @Synchronized fun containsType(@MagicConstant(valuesFromClass = MessageId::class) type: Int): Boolean { for (message in list) { if (message.type == type) { @@ -47,21 +46,18 @@ class MessageChain : Message { } fun stream(): Stream { - return ArrayList(list).stream() + return 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 { + override fun concat(tail: Message): MessageChain { this.list.add(tail) clearToStringCache() return this diff --git a/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/PlainText.kt b/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/PlainText.kt index 56506f6fc..e14938561 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/PlainText.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/PlainText.kt @@ -2,8 +2,8 @@ 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.network.packet.writeLVByteArray +import net.mamoe.mirai.network.packet.writeLVString import net.mamoe.mirai.utils.lazyEncode /** @@ -19,9 +19,9 @@ class PlainText(private val text: String) : Message() { override fun toByteArray(): ByteArray = lazyEncode { section -> section.writeByte(this.type) - section.writeVarByteArray(lazyEncode { child -> + section.writeLVByteArray(lazyEncode { child -> child.writeByte(0x01) - child.writeVarString(this.text) + child.writeLVString(this.text) }) } diff --git a/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/UnsolvedImage.kt b/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/UnsolvedImage.kt new file mode 100644 index 000000000..a8afd0295 --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/UnsolvedImage.kt @@ -0,0 +1,63 @@ +package net.mamoe.mirai.message.defaults + +import net.mamoe.mirai.contact.Contact +import net.mamoe.mirai.network.LoginSession +import net.mamoe.mirai.network.packet.image.ClientTryGetImageIDPacket +import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageFailedPacket +import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageResponsePacket +import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageSuccessPacket +import net.mamoe.mirai.network.packet.md5 +import net.mamoe.mirai.utils.ImageNetworkUtils +import net.mamoe.mirai.utils.toByteArray +import net.mamoe.mirai.utils.toUHexString +import java.awt.image.BufferedImage +import java.io.File +import java.net.URL +import java.util.concurrent.CompletableFuture +import javax.imageio.ImageIO + +/** + * 不确定是否存在于服务器的 [Image]. + * 必须在发送之前 [UnsolvedImage.upload] 或 [Contact.uploadImage], 否则会发送失败. +x * + * @author Him188moe + */ +class UnsolvedImage(filename: String, val image: BufferedImage) : Image(getImageId(filename)) { + constructor(imageFile: File) : this(imageFile.name, ImageIO.read(imageFile)) + constructor(url: URL) : this(File(url.file)) + + fun upload(session: LoginSession, contact: Contact): CompletableFuture { + return session.expectPacket { + toSend { ClientTryGetImageIDPacket(session.bot.account.qqNumber, session.sessionKey, session.bot.account.qqNumber, image) } + + expect { + when (it) { + is ServerTryUploadGroupImageFailedPacket -> { + //已经存在于服务器了 + } + + is ServerTryUploadGroupImageSuccessPacket -> { + val data = image.toByteArray() + if (!ImageNetworkUtils.postImage(it.uKey.toUHexString(), data.size, session.bot.account.qqNumber, contact.number, data)) { + throw RuntimeException("cannot upload image") + } + } + } + } + } + } + + companion object { + + @JvmStatic + fun getImageId(filename: String): String { + val md5 = md5(filename) + + return "{" + md5.copyOfRange(0, 4).toUHexString("") + "-" + .plus(md5.copyOfRange(4, 6).toUHexString("")) + "-" + .plus(md5.copyOfRange(6, 8).toUHexString("")) + "-" + .plus(md5.copyOfRange(8, 12).toUHexString("")) + "-" + .plus(md5.copyOfRange(12, 16).toUHexString("")) + "}." + if (filename.endsWith(".jpeg")) "jpg" else filename.substringAfter(".", "jpg") + } + } +} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandler.kt new file mode 100644 index 000000000..cbadca15e --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandler.kt @@ -0,0 +1,64 @@ +package net.mamoe.mirai.network + +import net.mamoe.mirai.network.BotNetworkHandlerImpl.BotSocket +import net.mamoe.mirai.network.BotNetworkHandlerImpl.Login +import net.mamoe.mirai.network.handler.* +import net.mamoe.mirai.network.packet.ClientPacket +import net.mamoe.mirai.network.packet.Packet +import net.mamoe.mirai.network.packet.ServerEventPacket +import net.mamoe.mirai.network.packet.ServerPacket +import net.mamoe.mirai.network.packet.login.LoginState +import java.io.Closeable +import java.util.concurrent.CompletableFuture + +/** + * Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务. + * [BotNetworkHandler] 是全异步和线程安全的. + * + * [BotNetworkHandler] 由 2 个模块构成: + * - [BotSocket]: 处理数据包底层的发送([ByteArray]) + * - [PacketHandler]: 制作 [ClientPacket] 并传递给 [BotSocket] 发送; 分析 [ServerPacket] 并处理 + * + * 其中, [PacketHandler] 由 4 个子模块构成: + * - [DebugPacketHandler] 输出 [Packet.toString] + * - [Login] 处理 touch/login/verification code 相关 + * - [MessagePacketHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket]) + * - [ActionPacketHandler] 处理动作相关(踢人/加入群/好友列表等) + * + * A BotNetworkHandler is used to connect with Tencent servers. + * + * @author Him188moe + */ +interface BotNetworkHandler : Closeable { + /** + * 网络层处理器. 用于编码/解码 [Packet], 发送/接受 [ByteArray] + * + * java 调用方式: `botNetWorkHandler.getSocket()` + */ + val socket: DataPacketSocket + + /** + * 消息处理. 如发送好友消息, 接受群消息并触发事件 + * + * java 调用方式: `botNetWorkHandler.getMessage()` + */ + var message: MessagePacketHandler + + /** + * 动作处理. 如发送好友请求, 处理别人发来的好友请求等 + * + * java 调用方式: `botNetWorkHandler.getAction()` + */ + var action: ActionPacketHandler + + fun tryLogin(touchingTimeoutMillis: Long = 200): CompletableFuture + + /** + * 添加一个临时包处理器 + * + * @see [TemporaryPacketHandler] + */ + fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>) + + override fun close() +} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandlerImpl.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandlerImpl.kt new file mode 100644 index 000000000..b22c10713 --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandlerImpl.kt @@ -0,0 +1,403 @@ +package net.mamoe.mirai.network + +import net.mamoe.mirai.Bot +import net.mamoe.mirai.MiraiServer +import net.mamoe.mirai.event.events.bot.BotLoginSucceedEvent +import net.mamoe.mirai.event.events.network.BeforePacketSendEvent +import net.mamoe.mirai.event.events.network.PacketSentEvent +import net.mamoe.mirai.event.events.network.ServerPacketReceivedEvent +import net.mamoe.mirai.network.handler.* +import net.mamoe.mirai.network.packet.* +import net.mamoe.mirai.network.packet.login.* +import net.mamoe.mirai.task.MiraiThreadPool +import net.mamoe.mirai.utils.* +import java.io.Closeable +import java.net.DatagramPacket +import java.net.DatagramSocket +import java.net.InetSocketAddress +import java.util.* +import java.util.concurrent.CompletableFuture +import java.util.concurrent.ScheduledFuture +import java.util.concurrent.TimeUnit +import javax.imageio.ImageIO + +/** + * [BotNetworkHandler] 的内部实现, 该类不会有帮助理解的注解, 请查看 [BotNetworkHandler] 以获取帮助 + * + * @see BotNetworkHandler + * @author Him188moe + */ +@Suppress("EXPERIMENTAL_API_USAGE")//to simplify code +internal class BotNetworkHandlerImpl(private val bot: Bot) : BotNetworkHandler { + override val socket: BotSocket = BotSocket() + + lateinit var login: Login + + override lateinit var message: MessagePacketHandler + override lateinit var action: ActionPacketHandler + + val packetHandlers: PacketHandlerList = PacketHandlerList() + + internal val temporaryPacketHandlers = Collections.synchronizedList(mutableListOf>()) + + override fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>) { + temporaryPacketHandler.send(action.session) + temporaryPacketHandlers.add(temporaryPacketHandler) + } + + //private | internal + /** + * 尝试登录 + * + * @param touchingTimeoutMillis 连接每个服务器的 timeout + */ + override fun tryLogin(touchingTimeoutMillis: Long): CompletableFuture { + val ipQueue: LinkedList = LinkedList(Protocol.SERVER_IP) + val future = CompletableFuture() + + fun login() { + this.socket.close() + val ip = ipQueue.poll() + if (ip == null) { + future.complete(LoginState.UNKNOWN)//所有服务器均返回 UNKNOWN + return + } + + this.socket.touch(ip, touchingTimeoutMillis).get().let { state -> + if (state == LoginState.UNKNOWN || state == LoginState.TIMEOUT) { + login()//超时或未知, 重试连接下一个服务器 + } else { + future.complete(state) + } + } + } + login() + return future + } + + private fun onLoggedIn(sessionKey: ByteArray) { + val session = LoginSession(bot, sessionKey, socket) + message = MessagePacketHandler(session) + action = ActionPacketHandler(session) + + packetHandlers.add(message.asNode()) + packetHandlers.add(action.asNode()) + } + + private lateinit var sessionKey: ByteArray + + override fun close() { + this.packetHandlers.forEach { + it.instance.close() + } + this.socket.close() + } + + + internal inner class BotSocket : Closeable, DataPacketSocket { + override fun distributePacket(packet: ServerPacket) { + try { + packet.decode() + } catch (e: java.lang.Exception) { + e.printStackTrace() + bot.debugPacket(packet) + return + } + + temporaryPacketHandlers.removeIf { + it.onPacketReceived(action.session, packet) + } + + //For debug + kotlin.run { + if (!packet.javaClass.name.endsWith("Encrypted") && !packet.javaClass.name.endsWith("Raw")) { + bot.notice("Packet received: $packet") + } + + if (packet is ServerEventPacket) { + sendPacket(ClientEventResponsePacket(bot.account.qqNumber, packet.packetId, sessionKey, packet.eventIdentity)) + } + } + + if (ServerPacketReceivedEvent(bot, packet).broadcast().isCancelled) { + return + } + + login.onPacketReceived(packet) + packetHandlers.forEach { + it.instance.onPacketReceived(packet) + } + } + + private var socket: DatagramSocket? = null + + internal var serverIP: String = "" + set(value) { + field = value + + restartSocket() + } + + internal var loginFuture: CompletableFuture? = null + + @Synchronized + private fun restartSocket() { + socket?.close() + socket = DatagramSocket(0) + socket!!.connect(InetSocketAddress(serverIP, 8000)) + Thread { + while (socket!!.isConnected) { + val packet = DatagramPacket(ByteArray(2048), 2048) + kotlin.runCatching { socket?.receive(packet) } + .onSuccess { + MiraiThreadPool.getInstance().submit { + try { + distributePacket(ServerPacket.ofByteArray(packet.data.removeZeroTail())) + } catch (e: Exception) { + e.printStackTrace() + } + } + }.onFailure { + if (it.message == "Socket closed" || it.message == "socket closed") { + return@Thread + } + it.printStackTrace() + } + + } + }.start() + } + + + /** + * Start network and touch the server + */ + fun touch(serverAddress: String, timeoutMillis: Long): CompletableFuture { + bot.info("Connecting server: $serverAddress") + if (this@BotNetworkHandlerImpl::login.isInitialized) { + login.close() + } + login = Login() + this.loginFuture = CompletableFuture() + + serverIP = serverAddress + bot.waitForPacket(ServerPacket::class, timeoutMillis) { + loginFuture!!.complete(LoginState.TIMEOUT) + } + sendPacket(ClientTouchPacket(bot.account.qqNumber, serverIP)) + + return this.loginFuture!! + } + + /** + * Not async + */ + @Synchronized + + override fun sendPacket(packet: ClientPacket) { + checkNotNull(socket) { "network closed" } + if (socket!!.isClosed) { + return + } + + try { + packet.encodePacket() + + if (BeforePacketSendEvent(bot, packet).broadcast().isCancelled) { + return + } + + val data = packet.toByteArray() + socket!!.send(DatagramPacket(data, data.size)) + bot.cyanL("Packet sent: $packet") + + PacketSentEvent(bot, packet).broadcast() + } catch (e: Throwable) { + e.printStackTrace() + } + } + + override fun getOwner(): Bot = this@BotNetworkHandlerImpl.bot + + override fun close() { + this.socket?.close() + if (this.loginFuture != null) { + if (!this.loginFuture!!.isDone) { + this.loginFuture!!.cancel(true) + } + this.loginFuture = null + } + } + + override fun isClosed(): Boolean { + return this.socket?.isClosed ?: true + } + } + + /** + * 处理登录过程 + */ + inner class Login : Closeable { + private lateinit var token00BA: ByteArray + private lateinit var token0825: ByteArray + private var loginTime: Int = 0 + private lateinit var loginIP: String + private var tgtgtKey: ByteArray = getRandomByteArray(16) + + /** + * 0828_decr_key + */ + private lateinit var sessionResponseDecryptionKey: ByteArray + + private var captchaSectionId: Int = 1 + private var captchaCache: ByteArray? = byteArrayOf()//每次包只发一部分验证码来 + + private var heartbeatFuture: ScheduledFuture<*>? = null + + + fun onPacketReceived(packet: ServerPacket) { + when (packet) { + is ServerTouchResponsePacket -> { + if (packet.serverIP != null) {//redirection + socket.serverIP = packet.serverIP!! + //connect(packet.serverIP!!) + socket.sendPacket(ClientServerRedirectionPacket(packet.serverIP!!, bot.account.qqNumber)) + } else {//password submission + this.loginIP = packet.loginIP + this.loginTime = packet.loginTime + this.token0825 = packet.token0825 + socket.sendPacket(ClientPasswordSubmissionPacket(bot.account.qqNumber, bot.account.password, packet.loginTime, packet.loginIP, this.tgtgtKey, packet.token0825)) + } + } + + is ServerLoginResponseFailedPacket -> { + socket.loginFuture?.complete(packet.loginState) + return + } + + is ServerVerificationCodeCorrectPacket -> { + this.tgtgtKey = getRandomByteArray(16) + this.token00BA = packet.token00BA + socket.sendPacket(ClientLoginResendPacket3105(bot.account.qqNumber, bot.account.password, this.loginTime, this.loginIP, this.tgtgtKey, this.token0825, this.token00BA)) + } + + is ServerLoginResponseVerificationCodeInitPacket -> { + //[token00BA]来源之一: 验证码 + this.token00BA = packet.token00BA + this.captchaCache = packet.verifyCodePart1 + + if (packet.unknownBoolean != null && packet.unknownBoolean!!) { + this.captchaSectionId = 1 + socket.sendPacket(ClientVerificationCodeTransmissionRequestPacket(1, bot.account.qqNumber, this.token0825, this.captchaSectionId++, this.token00BA)) + } + } + + is ServerVerificationCodeTransmissionPacket -> { + if (packet is ServerVerificationCodeWrongPacket) { + bot.error("验证码错误, 请重新输入") + captchaSectionId = 1 + this.captchaCache = byteArrayOf() + } + + this.captchaCache = this.captchaCache!! + packet.captchaSectionN + this.token00BA = packet.token00BA + + if (packet.transmissionCompleted) { + bot.notice(CharImageUtil.createCharImg(ImageIO.read(this.captchaCache!!.inputStream()))) + bot.notice("需要验证码登录, 验证码为 4 字母") + try { + (MiraiServer.getInstance().parentFolder + "VerificationCode.png").writeBytes(this.captchaCache!!) + bot.notice("若看不清字符图片, 请查看 Mirai 根目录下 VerificationCode.png") + } catch (e: Exception) { + bot.notice("无法写出验证码文件, 请尝试查看以上字符图片") + } + bot.notice("若要更换验证码, 请直接回车") + val code = Scanner(System.`in`).nextLine() + if (code.isEmpty() || code.length != 4) { + this.captchaCache = byteArrayOf() + this.captchaSectionId = 1 + socket.sendPacket(ClientVerificationCodeRefreshPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825)) + } else { + socket.sendPacket(ClientVerificationCodeSubmitPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, code, packet.verificationToken)) + } + } else { + socket.sendPacket(ClientVerificationCodeTransmissionRequestPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, captchaSectionId++, token00BA)) + } + } + + is ServerLoginResponseSuccessPacket -> { + this.sessionResponseDecryptionKey = packet.sessionResponseDecryptionKey + socket.sendPacket(ClientSessionRequestPacket(bot.account.qqNumber, socket.serverIP, packet.token38, packet.token88, packet.encryptionKey)) + } + + //是ClientPasswordSubmissionPacket之后服务器回复的 + is ServerLoginResponseKeyExchangePacket -> { + //if (packet.tokenUnknown != null) { + //this.token00BA = packet.token00BA!! + //println("token00BA changed!!! to " + token00BA.toUByteArray()) + //} + if (packet.flag == ServerLoginResponseKeyExchangePacket.Flag.`08 36 31 03`) { + this.tgtgtKey = packet.tgtgtKey + socket.sendPacket(ClientLoginResendPacket3104(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown + ?: this.token00BA, packet.tlv0006)) + } else { + socket.sendPacket(ClientLoginResendPacket3106(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown + ?: token00BA, packet.tlv0006)) + } + } + + is ServerSessionKeyResponsePacket -> { + sessionKey = packet.sessionKey + heartbeatFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({ + socket.sendPacket(ClientHeartbeatPacket(bot.account.qqNumber, sessionKey)) + }, 90000, 90000, TimeUnit.MILLISECONDS) + + socket.loginFuture!!.complete(LoginState.SUCCESS) + + login.changeOnlineStatus(ClientLoginStatus.ONLINE) + } + + is ServerLoginSuccessPacket -> { + BotLoginSucceedEvent(bot).broadcast() + + //登录成功后会收到大量上次的消息, 忽略掉 + MiraiThreadPool.getInstance().schedule({ + message.ignoreMessage = false + }, 3, TimeUnit.SECONDS) + + + onLoggedIn(sessionKey) + } + + + is ServerVerificationCodePacket.Encrypted -> socket.distributePacket(packet.decrypt()) + is ServerLoginResponseVerificationCodeInitPacket.Encrypted -> socket.distributePacket(packet.decrypt()) + is ServerLoginResponseKeyExchangePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.tgtgtKey)) + is ServerLoginResponseSuccessPacket.Encrypted -> socket.distributePacket(packet.decrypt(this.tgtgtKey)) + is ServerSessionKeyResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.sessionResponseDecryptionKey)) + is ServerTouchResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt()) + + + is ServerHeartbeatResponsePacket, + is UnknownServerPacket -> { + //ignored + } + else -> { + + } + } + } + + fun changeOnlineStatus(status: ClientLoginStatus) { + socket.sendPacket(ClientChangeOnlineStatusPacket(bot.account.qqNumber, sessionKey, status)) + } + + override fun close() { + this.captchaCache = null + + this.heartbeatFuture?.cancel(true) + + this.heartbeatFuture = null + } + } +} diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/LoginSession.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/LoginSession.kt new file mode 100644 index 000000000..dd86d3473 --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/LoginSession.kt @@ -0,0 +1,98 @@ +package net.mamoe.mirai.network + +import net.mamoe.mirai.Bot +import net.mamoe.mirai.network.handler.DataPacketSocket +import net.mamoe.mirai.network.handler.TemporaryPacketHandler +import net.mamoe.mirai.network.packet.ClientPacket +import net.mamoe.mirai.network.packet.ServerPacket +import net.mamoe.mirai.utils.getGTK +import java.util.concurrent.CompletableFuture + +/** + * 登录会话. 当登录完成后, 客户端会拿到 sessionKey. + * 此时建立 session, 然后开始处理事务. + * + * @author Him188moe + */ +class LoginSession( + val bot: Bot, + val sessionKey: ByteArray, + val socket: DataPacketSocket +) { + + /** + * Web api 使用 + */ + lateinit var cookies: String + + /** + * Web api 使用 + */ + var sKey: String = "" + set(value) { + field = value + gtk = getGTK(value) + } + + /** + * Web api 使用 + */ + var gtk: Int = 0 + + + /** + * 发送一个数据包, 并期待接受一个特定的 [ServerPacket]. 仅 Kotlin 使用 + * 发送成功后, 该方法会等待收到 [ServerPacket] 直到超时. + * + * 实现方法: + * ```kotlin + * session.expectPacket { + * toSend { ClientPacketXXX(...) } + * expect {//it: ServerPacketXXX + * + * } + * } + * ``` + * + * @param P 期待的包 + * @param handlerTemporary 处理器. + * @return future. 可进行超时处理 + * + * Kotlin DSL: 仅 Kotlin 使用. + */ + @JvmSynthetic + inline fun expectPacket(handlerTemporary: TemporaryPacketHandler

.() -> Unit): CompletableFuture { + val future = CompletableFuture() + this.bot.network.addHandler(TemporaryPacketHandler(P::class, future, this).also(handlerTemporary)) + return future + } + + /** + * 发送一个数据包, 并期待接受一个特定的 [ServerPacket]. + * 发送成功后, 该方法会等待收到 [ServerPacket] 直到超时. + * 由于包名可能过长, 可使用 `DataPacketSocket.expectPacket(PacketProcessor)` 替代. + * + * 实现方法: + * ```kotlin + * session.expectPacket(ClientPacketXXX(...)) {//it: ServerPacketXXX + * + * } + * ``` + * + * @param P 期待的包 + * @param toSend 将要发送的包 + * @param handler 处理期待的包 + * @return future. 可进行超时处理 + * + * Kotlin DSL: 仅 Kotlin 使用. + */ + @JvmSynthetic + inline fun expectPacket(toSend: ClientPacket, noinline handler: (P) -> Unit): CompletableFuture { + val future = CompletableFuture() + this.bot.network.addHandler(TemporaryPacketHandler(P::class, future, this).also { + it.toSend { toSend } + it.expect(handler) + }) + return future + } +} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/Protocol.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/Protocol.kt index 06e558b35..187d7327c 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/Protocol.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/Protocol.kt @@ -1,3 +1,5 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE") + package net.mamoe.mirai.network import java.net.InetAddress @@ -72,7 +74,7 @@ object Protocol { private val hexToByteArrayCacheMap: MutableMap = mutableMapOf() - @ExperimentalUnsignedTypes + fun hexToBytes(hex: String): ByteArray { hex.hashCode().let { id -> if (hexToByteArrayCacheMap.containsKey(id)) { @@ -86,7 +88,7 @@ object Protocol { } } - @ExperimentalUnsignedTypes + fun hexToUBytes(hex: String): UByteArray = Arrays .stream(hex.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) .map { value -> value.trim { it <= ' ' } } diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/RobotNetworkHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/RobotNetworkHandler.kt deleted file mode 100644 index 2ae1745b3..000000000 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/RobotNetworkHandler.kt +++ /dev/null @@ -1,645 +0,0 @@ -@file:JvmMultifileClass -@file:JvmName("BotNetworkHandler") - -package net.mamoe.mirai.network - -import net.mamoe.mirai.Bot -import net.mamoe.mirai.MiraiServer -import net.mamoe.mirai.contact.Group -import net.mamoe.mirai.contact.QQ -import net.mamoe.mirai.event.events.bot.BotLoginSucceedEvent -import net.mamoe.mirai.event.events.network.BeforePacketSendEvent -import net.mamoe.mirai.event.events.network.PacketSentEvent -import net.mamoe.mirai.event.events.network.ServerPacketReceivedEvent -import net.mamoe.mirai.event.events.qq.FriendMessageEvent -import net.mamoe.mirai.event.hookWhile -import net.mamoe.mirai.message.Message -import net.mamoe.mirai.message.defaults.MessageChain -import net.mamoe.mirai.network.BotNetworkHandler.* -import net.mamoe.mirai.network.packet.* -import net.mamoe.mirai.network.packet.action.* -import net.mamoe.mirai.network.packet.login.* -import net.mamoe.mirai.task.MiraiThreadPool -import net.mamoe.mirai.utils.* -import java.io.Closeable -import java.net.DatagramPacket -import java.net.DatagramSocket -import java.net.InetSocketAddress -import java.util.* -import java.util.concurrent.CompletableFuture -import java.util.concurrent.ScheduledFuture -import java.util.concurrent.TimeUnit -import java.util.function.Supplier -import javax.imageio.ImageIO -import kotlin.reflect.KClass - - -/** - * Mirai 的网络处理器, 它处理所有数据包([Packet])的发送和接收. - * [BotNetworkHandler] 是全程异步和线程安全的. - * - * [BotNetworkHandler] 由 2 个模块构成: - * - [SocketHandler]: 处理数据包底层的发送([ByteArray]) - * - [PacketHandler]: 制作 [Packet] 并传递给 [SocketHandler] 继续处理; 分析来自服务器的数据包并处理 - * - * 其中, [PacketHandler] 由 4 个子模块构成: - * - [DebugHandler] 输出 [Packet.toString] - * - [LoginHandler] 处理 touch/login/verification code 相关 - * - [MessageHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket]) - * - [ActionHandler] 处理动作相关(踢人/加入群/好友列表等) - * - * A BotNetworkHandler is used to connect with Tencent servers. - * - * @author Him188moe - */ -@Suppress("EXPERIMENTAL_API_USAGE")//to simplify code -class BotNetworkHandler(private val bot: Bot) : Closeable { - private val socketHandler: SocketHandler = SocketHandler() - - val debugHandler = DebugHandler() - val loginHandler = LoginHandler() - val messageHandler = MessageHandler() - val actionHandler = ActionHandler() - - private val packetHandlers: Map, PacketHandler> = linkedMapOf( - DebugHandler::class to debugHandler, - LoginHandler::class to loginHandler, - MessageHandler::class to messageHandler, - ActionHandler::class to actionHandler - ) - - /** - * Not async - */ - @ExperimentalUnsignedTypes - fun sendPacket(packet: ClientPacket) { - socketHandler.sendPacket(packet) - } - - override fun close() { - this.packetHandlers.values.forEach { - it.close() - } - this.socketHandler.close() - } - - - //private | internal - - /** - * 仅当 [LoginState] 非 [LoginState.UNKNOWN] 且非 [LoginState.TIMEOUT] 才会调用 [loginHook]. - * 如果要输入验证码, 那么会以参数 [LoginState.VERIFICATION_CODE] 调用 [loginHandler], 登录完成后再以 [LoginState.SUCCESS] 调用 [loginHandler] - * - * @param touchingTimeoutMillis 连接每个服务器的 timeout - */ - @JvmOverloads - internal fun tryLogin(touchingTimeoutMillis: Long = 200): CompletableFuture { - val ipQueue: LinkedList = LinkedList(Protocol.SERVER_IP) - val future = CompletableFuture() - - fun login() { - this.socketHandler.close() - val ip = ipQueue.poll() - if (ip == null) { - future.complete(LoginState.UNKNOWN)//所有服务器均返回 UNKNOWN - return - } - - this@BotNetworkHandler.socketHandler.touch(ip, touchingTimeoutMillis).get().let { state -> - if (state == LoginState.UNKNOWN || state == LoginState.TIMEOUT) { - login() - } else { - future.complete(state) - } - } - } - login() - return future - } - - /** - * 分配收到的数据包 - */ - @ExperimentalUnsignedTypes - internal fun distributePacket(packet: ServerPacket) { - try { - packet.decode() - } catch (e: java.lang.Exception) { - e.printStackTrace() - bot.debug("Packet=$packet") - bot.debug("Packet size=" + packet.input.goto(0).readAllBytes().size) - bot.debug("Packet data=" + packet.input.goto(0).readAllBytes().toUHexString()) - return - } - - if (ServerPacketReceivedEvent(bot, packet).broadcast().isCancelled) { - debugHandler.onPacketReceived(packet) - return - } - this.packetHandlers.values.forEach { - it.onPacketReceived(packet) - } - } - - - private inner class SocketHandler : Closeable { - private var socket: DatagramSocket? = null - - internal var serverIP: String = "" - set(value) { - field = value - - restartSocket() - } - - internal var loginFuture: CompletableFuture? = null - - @Synchronized - private fun restartSocket() { - socket?.close() - socket = DatagramSocket(0) - socket!!.connect(InetSocketAddress(serverIP, 8000)) - Thread { - while (socket!!.isConnected) { - val packet = DatagramPacket(ByteArray(2048), 2048) - kotlin.runCatching { socket?.receive(packet) } - .onSuccess { - MiraiThreadPool.getInstance().submit { - try { - distributePacket(ServerPacket.ofByteArray(packet.data.removeZeroTail())) - } catch (e: Exception) { - e.printStackTrace() - } - } - }.onFailure { - if (it.message == "Socket closed" || it.message == "socket closed") { - return@Thread - } - it.printStackTrace() - } - - } - }.start() - } - - /** - * Start network and touch the server - */ - internal fun touch(serverAddress: String, timeoutMillis: Long): CompletableFuture { - bot.info("Connecting server: $serverAddress") - this.loginFuture = CompletableFuture() - - socketHandler.serverIP = serverAddress - waitForPacket(ServerPacket::class, timeoutMillis) { - loginFuture!!.complete(LoginState.TIMEOUT) - } - sendPacket(ClientTouchPacket(bot.account.qqNumber, socketHandler.serverIP)) - - return this.loginFuture!! - } - - /** - * Not async - */ - @Synchronized - @ExperimentalUnsignedTypes - internal fun sendPacket(packet: ClientPacket) { - checkNotNull(socket) { "network closed" } - if (socket!!.isClosed) { - return - } - - try { - packet.encodePacket() - - if (BeforePacketSendEvent(bot, packet).broadcast().isCancelled) { - return - } - - val data = packet.toByteArray() - socket!!.send(DatagramPacket(data, data.size)) - bot cyanL "Packet sent: $packet" - - PacketSentEvent(bot, packet).broadcast() - } catch (e: Throwable) { - e.printStackTrace() - } - } - - @Suppress("UNCHECKED_CAST") - internal fun

waitForPacket(packetClass: KClass

, timeoutMillis: Long, timeout: () -> Unit) { - var got = false - ServerPacketReceivedEvent::class.hookWhile { - if (packetClass.isInstance(it.packet) && it.bot == bot) { - got = true - true - } else { - false - } - } - - - MiraiThreadPool.getInstance().submit { - val startingTime = System.currentTimeMillis() - while (!got) { - if (System.currentTimeMillis() - startingTime > timeoutMillis) { - timeout.invoke() - return@submit - } - Thread.sleep(10) - } - } - } - - override fun close() { - this.socket?.close() - if (this.loginFuture != null) { - if (!this.loginFuture!!.isDone) { - this.loginFuture!!.cancel(true) - } - this.loginFuture = null - } - } - - fun isClosed(): Boolean { - return this.socket?.isClosed ?: true - } - } - - - private lateinit var sessionKey: ByteArray - - abstract inner class PacketHandler : Closeable { - abstract fun onPacketReceived(packet: ServerPacket) - - override fun close() { - - } - } - - /** - * Kind of [PacketHandler] that prints all packets received in the format of hex byte array. - */ - inner class DebugHandler : PacketHandler() { - override fun onPacketReceived(packet: ServerPacket) { - if (!packet.javaClass.name.endsWith("Encrypted") && !packet.javaClass.name.endsWith("Raw")) { - bot notice "Packet received: $packet" - } - if (packet is ServerEventPacket) { - sendPacket(ClientMessageResponsePacket(bot.account.qqNumber, packet.packetId, sessionKey, packet.eventIdentity)) - } - } - } - - /** - * 处理登录过程 - */ - inner class LoginHandler : PacketHandler() { - private lateinit var token00BA: ByteArray - private lateinit var token0825: ByteArray - private var loginTime: Int = 0 - private lateinit var loginIP: String - private var tgtgtKey: ByteArray = getRandomByteArray(16) - - private var tlv0105: ByteArray = lazyEncode { - it.writeHex("01 05 00 30") - it.writeHex("00 01 01 02 00 14 01 01 00 10") - it.writeRandom(16) - it.writeHex("00 14 01 02 00 10") - it.writeRandom(16) - } - - /** - * 0828_decr_key - */ - private lateinit var sessionResponseDecryptionKey: ByteArray - - private var captchaSectionId: Int = 1 - private var captchaCache: ByteArray? = byteArrayOf()//每次包只发一部分验证码来 - - - private var heartbeatFuture: ScheduledFuture<*>? = null - private var sKeyRefresherFuture: ScheduledFuture<*>? = null - - override fun onPacketReceived(packet: ServerPacket) { - when (packet) { - is ServerTouchResponsePacket -> { - if (packet.serverIP != null) {//redirection - socketHandler.serverIP = packet.serverIP!! - //connect(packet.serverIP!!) - sendPacket(ClientServerRedirectionPacket(packet.serverIP!!, bot.account.qqNumber)) - } else {//password submission - this.loginIP = packet.loginIP - this.loginTime = packet.loginTime - this.token0825 = packet.token0825 - sendPacket(ClientPasswordSubmissionPacket(bot.account.qqNumber, bot.account.password, packet.loginTime, packet.loginIP, this.tgtgtKey, packet.token0825)) - } - } - - is ServerLoginResponseFailedPacket -> { - socketHandler.loginFuture?.complete(packet.loginState) - return - } - - is ServerVerificationCodeCorrectPacket -> { - this.tgtgtKey = getRandomByteArray(16) - this.token00BA = packet.token00BA - sendPacket(ClientLoginResendPacket3105(bot.account.qqNumber, bot.account.password, this.loginTime, this.loginIP, this.tgtgtKey, this.token0825, this.token00BA)) - } - - is ServerLoginResponseVerificationCodeInitPacket -> { - //[token00BA]来源之一: 验证码 - this.token00BA = packet.token00BA - this.captchaCache = packet.verifyCodePart1 - - if (packet.unknownBoolean != null && packet.unknownBoolean!!) { - this.captchaSectionId = 1 - sendPacket(ClientVerificationCodeTransmissionRequestPacket(1, bot.account.qqNumber, this.token0825, this.captchaSectionId++, this.token00BA)) - } - } - - is ServerVerificationCodeTransmissionPacket -> { - if (packet is ServerVerificationCodeWrongPacket) { - bot error "验证码错误, 请重新输入" - captchaSectionId = 1 - this.captchaCache = byteArrayOf() - } - - this.captchaCache = this.captchaCache!! + packet.captchaSectionN - this.token00BA = packet.token00BA - - if (packet.transmissionCompleted) { - bot notice (CharImageUtil.createCharImg(ImageIO.read(this.captchaCache!!.inputStream()))) - bot notice ("需要验证码登录, 验证码为 4 字母") - try { - (MiraiServer.getInstance().parentFolder + "VerificationCode.png").writeBytes(this.captchaCache!!) - bot notice ("若看不清字符图片, 请查看 Mirai 根目录下 VerificationCode.png") - } catch (e: Exception) { - bot notice "无法写出验证码文件, 请尝试查看以上字符图片" - } - bot notice ("若要更换验证码, 请直接回车") - val code = Scanner(System.`in`).nextLine() - if (code.isEmpty() || code.length != 4) { - this.captchaCache = byteArrayOf() - this.captchaSectionId = 1 - sendPacket(ClientVerificationCodeRefreshPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825)) - } else { - sendPacket(ClientVerificationCodeSubmitPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, code, packet.verificationToken)) - } - } else { - sendPacket(ClientVerificationCodeTransmissionRequestPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, captchaSectionId++, token00BA)) - } - } - - is ServerLoginResponseSuccessPacket -> { - this.sessionResponseDecryptionKey = packet.sessionResponseDecryptionKey - sendPacket(ClientSessionRequestPacket(bot.account.qqNumber, socketHandler.serverIP, packet.token38, packet.token88, packet.encryptionKey, this.tlv0105)) - } - - //是ClientPasswordSubmissionPacket之后服务器回复的 - is ServerLoginResponseKeyExchangePacket -> { - //if (packet.tokenUnknown != null) { - //this.token00BA = packet.token00BA!! - //println("token00BA changed!!! to " + token00BA.toUByteArray()) - //} - if (packet.flag == ServerLoginResponseKeyExchangePacket.Flag.`08 36 31 03`) { - this.tgtgtKey = packet.tgtgtKey - sendPacket(ClientLoginResendPacket3104(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown - ?: this.token00BA, packet.tlv0006)) - } else { - sendPacket(ClientLoginResendPacket3106(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown - ?: token00BA, packet.tlv0006)) - } - } - - is ServerSessionKeyResponsePacket -> { - sessionKey = packet.sessionKey - heartbeatFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({ - sendPacket(ClientHeartbeatPacket(bot.account.qqNumber, sessionKey)) - }, 90000, 90000, TimeUnit.MILLISECONDS) - - BotLoginSucceedEvent(bot).broadcast() - - //登录成功后会收到大量上次的消息, 忽略掉 - MiraiThreadPool.getInstance().schedule({ - messageHandler.ignoreMessage = false - }, 3, TimeUnit.SECONDS) - - this.tlv0105 = packet.tlv0105 - sendPacket(ClientChangeOnlineStatusPacket(bot.account.qqNumber, sessionKey, ClientLoginStatus.ONLINE)) - } - - is ServerLoginSuccessPacket -> { - socketHandler.loginFuture!!.complete(LoginState.SUCCESS) - sendPacket(ClientSKeyRequestPacket(bot.account.qqNumber, sessionKey)) - } - - is ServerSKeyResponsePacket -> { - actionHandler.sKey = packet.sKey - actionHandler.cookies = "uin=o" + bot.account.qqNumber + ";skey=" + actionHandler.sKey + ";" - - sKeyRefresherFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({ - sendPacket(ClientSKeyRefreshmentRequestPacket(bot.account.qqNumber, sessionKey)) - }, 1800000, 1800000, TimeUnit.MILLISECONDS) - - actionHandler.gtk = getGTK(actionHandler.sKey) - sendPacket(ClientAccountInfoRequestPacket(bot.account.qqNumber, sessionKey)) - } - - is ServerEventPacket.Raw -> distributePacket(packet.distribute()) - - is ServerVerificationCodePacket.Encrypted -> distributePacket(packet.decrypt()) - is ServerLoginResponseVerificationCodeInitPacket.Encrypted -> distributePacket(packet.decrypt()) - is ServerLoginResponseKeyExchangePacket.Encrypted -> distributePacket(packet.decrypt(this.tgtgtKey)) - is ServerLoginResponseSuccessPacket.Encrypted -> distributePacket(packet.decrypt(this.tgtgtKey)) - is ServerSessionKeyResponsePacket.Encrypted -> distributePacket(packet.decrypt(this.sessionResponseDecryptionKey)) - is ServerTouchResponsePacket.Encrypted -> distributePacket(packet.decrypt()) - is ServerSKeyResponsePacket.Encrypted -> distributePacket(packet.decrypt(sessionKey)) - is ServerAccountInfoResponsePacket.Encrypted -> distributePacket(packet.decrypt(sessionKey)) - is ServerEventPacket.Raw.Encrypted -> distributePacket(packet.decrypt(sessionKey)) - - - is ServerAccountInfoResponsePacket, - is ServerHeartbeatResponsePacket, - is UnknownServerPacket -> { - //ignored - } - else -> { - - } - } - } - - override fun close() { - this.captchaCache = null - - this.heartbeatFuture?.cancel(true) - this.sKeyRefresherFuture?.cancel(true) - - this.heartbeatFuture = null - this.sKeyRefresherFuture = null - } - } - - /** - * 处理消息事件, 承担消息发送任务. - */ - inner class MessageHandler : PacketHandler() { - internal var ignoreMessage: Boolean = true - - init { - //todo for test - FriendMessageEvent::class.hookWhile { - if (socketHandler.isClosed()) { - return@hookWhile false - } - if (it.message() valueEquals "你好") { - it.qq.sendMessage("你好!") - } else if (it.message().toString().startsWith("复读")) { - it.qq.sendMessage(it.message()) - } - - return@hookWhile true - } - } - - override fun onPacketReceived(packet: ServerPacket) { - when (packet) { - is ServerGroupUploadFileEventPacket -> { - //todo - } - - is ServerFriendMessageEventPacket -> { - if (ignoreMessage) { - return - } - - FriendMessageEvent(bot, bot.contacts.getQQ(packet.qq), packet.message).broadcast() - } - - is ServerGroupMessageEventPacket -> { - //todo message chain - //GroupMessageEvent(this.bot, bot.contacts.getGroupByNumber(packet.groupNumber), bot.contacts.getQQ(packet.qq), packet.message) - } - - is UnknownServerEventPacket -> { - //todo - } - - is ServerSendFriendMessageResponsePacket, - is ServerSendGroupMessageResponsePacket -> { - //ignored - } - else -> { - //ignored - } - } - } - - fun sendFriendMessage(qq: QQ, message: MessageChain) { - sendPacket(ClientSendFriendMessagePacket(bot.account.qqNumber, qq.number, sessionKey, message)) - } - - fun sendGroupMessage(group: Group, message: Message): Unit { - TODO() - //sendPacket(ClientSendGroupMessagePacket(group.groupId, bot.account.qqNumber, sessionKey, message)) - } - } - - /** - * 动作: 获取好友列表, 点赞, 踢人等. - * 处理动作事件, 承担动作任务. - */ - inner class ActionHandler : PacketHandler() { - internal lateinit var cookies: String - internal var sKey: String = "" - set(value) { - field = value - gtk = getGTK(value) - } - internal var gtk: Int = 0 - - private val addFriendSessions = Collections.synchronizedCollection(mutableListOf()) - - override fun onPacketReceived(packet: ServerPacket) { - when (packet) { - is ServerCanAddFriendResponsePacket -> { - this.addFriendSessions.forEach { - it.onPacketReceived(packet) - } - } - else -> { - } - } - } - - fun addFriend(qqNumber: Long, message: Supplier) { - addFriend(qqNumber, lazy { message.get() }) - } - - @JvmSynthetic - fun addFriend(qqNumber: Long, message: Lazy = lazyOf("")): CompletableFuture { - val future = CompletableFuture() - val session = AddFriendSession(qqNumber, future, message) - addFriendSessions.add(session) - session.sendAddRequest(); - return future - } - - override fun close() { - - } - - private inner class AddFriendSession( - private val qq: Long, - private val future: CompletableFuture, - private val message: Lazy - ) : Closeable { - lateinit var id: ByteArray - - fun onPacketReceived(packet: ServerPacket) { - if (!::id.isInitialized) { - return - } - - when (packet) { - is ServerCanAddFriendResponsePacket -> { - if (!(packet.idByteArray[2] == id[0] && packet.idByteArray[3] == id[1])) { - return - } - - when (packet.state) { - ServerCanAddFriendResponsePacket.State.FAILED -> { - future.complete(AddFriendResult.FAILED) - close() - } - - ServerCanAddFriendResponsePacket.State.ALREADY_ADDED -> { - future.complete(AddFriendResult.ALREADY_ADDED) - close() - } - - ServerCanAddFriendResponsePacket.State.REQUIRE_VERIFICATION -> { - sendPacket(ClientAddFriendPacket(bot.account.qqNumber, qq, sessionKey)) - } - - ServerCanAddFriendResponsePacket.State.NOT_REQUIRE_VERIFICATION -> { - - } - } - } - - - } - } - - fun sendAddRequest() { - sendPacket(ClientCanAddFriendPacket(bot.account.qqNumber, qq, sessionKey).also { this.id = it.packetIdLast }) - } - - override fun close() { - addFriendSessions.remove(this) - } - } - } -} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/ActionPacketHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/ActionPacketHandler.kt new file mode 100644 index 000000000..bd68f3323 --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/ActionPacketHandler.kt @@ -0,0 +1,213 @@ +package net.mamoe.mirai.network.handler + +import net.mamoe.mirai.network.LoginSession +import net.mamoe.mirai.network.packet.* +import net.mamoe.mirai.network.packet.action.AddFriendResult +import net.mamoe.mirai.network.packet.action.ClientAddFriendPacket +import net.mamoe.mirai.network.packet.action.ClientCanAddFriendPacket +import net.mamoe.mirai.network.packet.action.ServerCanAddFriendResponsePacket +import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageFailedPacket +import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageResponsePacket +import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageSuccessPacket +import net.mamoe.mirai.task.MiraiThreadPool +import net.mamoe.mirai.utils.getGTK +import java.awt.image.BufferedImage +import java.io.Closeable +import java.util.* +import java.util.concurrent.CompletableFuture +import java.util.concurrent.ScheduledFuture +import java.util.concurrent.TimeUnit +import java.util.function.Supplier + +/** + * 动作: 获取好友列表, 点赞, 踢人等. + * 处理动作事件, 承担动作任务. + * + * @author Him188moe + */ +class ActionPacketHandler(session: LoginSession) : PacketHandler(session) { + private val addFriendSessions = Collections.synchronizedCollection(mutableListOf()) + private val uploadImageSessions = Collections.synchronizedCollection(mutableListOf()) + + private var sKeyRefresherFuture: ScheduledFuture<*>? = null + + + override fun onPacketReceived(packet: ServerPacket) { + when (packet) { + is ServerCanAddFriendResponsePacket -> { + this.uploadImageSessions.forEach { + it.onPacketReceived(packet) + } + } + is ServerTryUploadGroupImageSuccessPacket -> { + // ImageNetworkUtils.postImage(packet.uKey.toUHexString(), ) + } + + is ServerTryUploadGroupImageFailedPacket -> { + + } + + is ServerTryUploadGroupImageResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey)) + + is ServerAccountInfoResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey)) + is ServerAccountInfoResponsePacket -> { + + } + + is ServerSKeyResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey)) + is ServerSKeyResponsePacket -> { + session.sKey = packet.sKey + session.cookies = "uin=o" + session.bot.account.qqNumber + ";skey=" + session.sKey + ";" + + sKeyRefresherFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({ + session.socket.sendPacket(ClientSKeyRefreshmentRequestPacket(session.bot.account.qqNumber, session.sessionKey)) + }, 1800000, 1800000, TimeUnit.MILLISECONDS) + + session.gtk = getGTK(session.sKey) + } + + is ServerEventPacket.Raw.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey)) + is ServerEventPacket.Raw -> session.socket.distributePacket(packet.distribute()) + + else -> { + } + } + } + + + fun addFriend(qqNumber: Long, message: Supplier) { + addFriend(qqNumber, lazy { message.get() }) + } + + + @JvmSynthetic + fun addFriend(qqNumber: Long, message: Lazy = lazyOf("")): CompletableFuture { + val future = CompletableFuture() + val session = AddFriendSession(qqNumber, future, message) + // uploadImageSessions.add(session) + session.sendAddRequest(); + return future + } + + + fun requestSKey() { + session.socket.sendPacket(ClientSKeyRequestPacket(session.bot.account.qqNumber, session.sessionKey)) + } + + + fun requestAccountInfo() { + session.socket.sendPacket(ClientAccountInfoRequestPacket(session.bot.account.qqNumber, session.sessionKey)) + } + + override fun close() { + this.sKeyRefresherFuture?.cancel(true) + this.sKeyRefresherFuture = null + } + + private inner class UploadImageSession( + private val group: Long, + private val future: CompletableFuture, + private val image: BufferedImage + ) : Closeable { + lateinit var id: ByteArray + + + fun onPacketReceived(packet: ServerPacket) { + if (!::id.isInitialized) { + return + } + + when (packet) { + is ServerCanAddFriendResponsePacket -> { + if (!(packet.idByteArray[2] == id[0] && packet.idByteArray[3] == id[1])) { + return + } + + when (packet.state) { + ServerCanAddFriendResponsePacket.State.FAILED -> { + future.complete(AddFriendResult.FAILED) + close() + } + + ServerCanAddFriendResponsePacket.State.ALREADY_ADDED -> { + future.complete(AddFriendResult.ALREADY_ADDED) + close() + } + + ServerCanAddFriendResponsePacket.State.REQUIRE_VERIFICATION -> { + // session.socket.sendPacket(ClientAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey)) + } + + ServerCanAddFriendResponsePacket.State.NOT_REQUIRE_VERIFICATION -> { + + } + } + } + + + } + } + + fun sendRequest() { + + } + + override fun close() { + uploadImageSessions.remove(this) + } + } + + private inner class AddFriendSession( + private val qq: Long, + private val future: CompletableFuture, + private val message: Lazy + ) : Closeable { + lateinit var id: ByteArray + + + fun onPacketReceived(packet: ServerPacket) { + if (!::id.isInitialized) { + return + } + + when (packet) { + is ServerCanAddFriendResponsePacket -> { + if (!(packet.idByteArray[2] == id[0] && packet.idByteArray[3] == id[1])) { + return + } + + when (packet.state) { + ServerCanAddFriendResponsePacket.State.FAILED -> { + future.complete(AddFriendResult.FAILED) + close() + } + + ServerCanAddFriendResponsePacket.State.ALREADY_ADDED -> { + future.complete(AddFriendResult.ALREADY_ADDED) + close() + } + + ServerCanAddFriendResponsePacket.State.REQUIRE_VERIFICATION -> { + session.socket.sendPacket(ClientAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey)) + } + + ServerCanAddFriendResponsePacket.State.NOT_REQUIRE_VERIFICATION -> { + + } + } + } + + + } + } + + + fun sendAddRequest() { + session.socket.sendPacket(ClientCanAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey).also { this.id = it.packetIdLast }) + } + + override fun close() { + // uploadImageSessions.remove(this) + } + } +} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/DataPacketSocket.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/DataPacketSocket.kt new file mode 100644 index 000000000..4eb19c20c --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/DataPacketSocket.kt @@ -0,0 +1,53 @@ +package net.mamoe.mirai.network.handler + +import net.mamoe.mirai.Bot +import net.mamoe.mirai.event.events.network.ServerPacketReceivedEvent +import net.mamoe.mirai.network.BotNetworkHandlerImpl +import net.mamoe.mirai.network.LoginSession +import net.mamoe.mirai.network.packet.ClientPacket +import net.mamoe.mirai.network.packet.ServerPacket +import net.mamoe.mirai.task.MiraiThreadPool +import java.io.Closeable +import java.util.concurrent.Future + +/** + * 网络接口. + * 发包 / 处理包. + * 仅可通过 [BotNetworkHandlerImpl.socket] 得到实例. + * + * @author Him188moe + */ +interface DataPacketSocket : Closeable { + fun getOwner(): Bot + + /** + * 分发数据包给 [PacketHandler] + */ + fun distributePacket(packet: ServerPacket) + + /** + * 发送一个数据包(非异步). + * + * 可通过 hook 事件 [ServerPacketReceivedEvent] 来获取服务器返回. + * + * @see [LoginSession.expectPacket] kotlin DSL + */ + fun sendPacket(packet: ClientPacket) + + /** + * 发送一个数据包(异步). + * + * 可通过 hook 事件 [ServerPacketReceivedEvent] 来获取服务器返回. + * + * @see [LoginSession.expectPacket] kotlin DSL + */ + fun sendPacketAsync(packet: ClientPacket): Future<*> { + return MiraiThreadPool.getInstance().submit { + sendPacket(packet) + } + } + + fun isClosed(): Boolean + + override fun close() +} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/MessagePacketHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/MessagePacketHandler.kt new file mode 100644 index 000000000..728bd20c3 --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/MessagePacketHandler.kt @@ -0,0 +1,91 @@ +package net.mamoe.mirai.network.handler + +import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.QQ +import net.mamoe.mirai.event.events.qq.FriendMessageEvent +import net.mamoe.mirai.event.hookWhile +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.packet.* +import net.mamoe.mirai.network.packet.action.ClientSendFriendMessagePacket +import net.mamoe.mirai.network.packet.action.ClientSendGroupMessagePacket +import net.mamoe.mirai.network.packet.action.ServerSendFriendMessageResponsePacket +import net.mamoe.mirai.network.packet.action.ServerSendGroupMessageResponsePacket +import java.io.File + +/** + * 处理消息事件, 承担消息发送任务. + * + * @author Him188moe + */ +@Suppress("EXPERIMENTAL_API_USAGE") +class MessagePacketHandler(session: LoginSession) : PacketHandler(session) { + internal var ignoreMessage: Boolean = true + + init { + //todo for test + FriendMessageEvent::class.hookWhile { + if (session.socket.isClosed()) { + return@hookWhile false + } + when { + it.message() valueEquals "你好" -> it.qq.sendMessage("你好!") + it.message().toString().startsWith("复读") -> it.qq.sendMessage(it.message()) + it.message().toString().startsWith("发群") -> { + it.message().list.toMutableList().let { messages -> + messages.removeAt(0) + sendGroupMessage(Group(session.bot, 580266363), MessageChain(messages)) + } + } + it.message() valueEquals "发图片" -> sendGroupMessage(Group(session.bot, 580266363), PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image -> + image.upload(session, it.qq).get() + }) + } + + return@hookWhile true + } + } + + override fun onPacketReceived(packet: ServerPacket) { + when (packet) { + is ServerGroupUploadFileEventPacket -> { + //todo + } + + is ServerFriendMessageEventPacket -> { + if (ignoreMessage) { + return + } + + FriendMessageEvent(session.bot, session.bot.contacts.getQQ(packet.qq), packet.message).broadcast() + } + + is ServerGroupMessageEventPacket -> { + //todo message chain + //GroupMessageEvent(this.bot, bot.contacts.getGroupByNumber(packet.groupNumber), bot.contacts.getQQ(packet.qq), packet.message) + } + + is UnknownServerEventPacket -> { + //todo + } + + is ServerSendFriendMessageResponsePacket, + is ServerSendGroupMessageResponsePacket -> { + //ignored + } + else -> { + //ignored + } + } + } + + fun sendFriendMessage(qq: QQ, message: MessageChain) { + session.socket.sendPacketAsync(ClientSendFriendMessagePacket(session.bot.account.qqNumber, qq.number, session.sessionKey, message)) + } + + fun sendGroupMessage(group: Group, message: MessageChain) { + session.socket.sendPacket(ClientSendGroupMessagePacket(group.groupId, session.bot.account.qqNumber, session.sessionKey, message)) + } +} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/PacketHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/PacketHandler.kt new file mode 100644 index 000000000..1ff85ce95 --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/PacketHandler.kt @@ -0,0 +1,42 @@ +package net.mamoe.mirai.network.handler + +import net.mamoe.mirai.network.LoginSession +import net.mamoe.mirai.network.packet.ServerPacket +import net.mamoe.mirai.utils.MiraiSynchronizedLinkedList +import java.io.Closeable + +/** + * 数据包(接受/发送)处理器 + */ +abstract class PacketHandler( + val session: LoginSession +) : Closeable { + abstract fun onPacketReceived(packet: ServerPacket) + + override fun close() { + + } +} + +class PacketHandlerNode( + val clazz: Class, + val instance: T +) + +fun PacketHandler.asNode(): PacketHandlerNode { + return PacketHandlerNode(this.javaClass, this) +} + +class PacketHandlerList : MiraiSynchronizedLinkedList>() { + + fun get(clazz: Class): T { + this.forEach { + if (it.clazz == clazz) { + @Suppress("UNCHECKED_CAST") + return@get it.instance as T + } + } + + throw NoSuchElementException() + } +} diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/TemporaryPacketHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/TemporaryPacketHandler.kt new file mode 100644 index 000000000..3e6b06d95 --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/TemporaryPacketHandler.kt @@ -0,0 +1,49 @@ +package net.mamoe.mirai.network.handler + +import net.mamoe.mirai.network.LoginSession +import net.mamoe.mirai.network.packet.ClientPacket +import net.mamoe.mirai.network.packet.ServerPacket +import java.util.concurrent.CompletableFuture +import kotlin.reflect.KClass + +/** + * 临时数据包处理器 + * + * @see LoginSession.expectPacket + */ +open class TemporaryPacketHandler

( + private val expectationClass: KClass

, + private val future: CompletableFuture, + private val fromSession: LoginSession +) { + private lateinit var toSend: ClientPacket + + private lateinit var expect: (P) -> Unit + + + lateinit var session: LoginSession//无需覆盖 + + fun toSend(packet: () -> ClientPacket) { + this.toSend = packet() + } + + + fun expect(handler: (P) -> Unit) { + this.expect = handler + } + + fun send(session: LoginSession) { + this.session = session + session.socket.sendPacket(toSend) + } + + fun onPacketReceived(session: LoginSession, packet: ServerPacket): Boolean { + if (expectationClass.isInstance(packet) && session === this.fromSession) { + @Suppress("UNCHECKED_CAST") + expect(packet as P) + future.complete(Unit) + return true + } + return false + } +} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ClientPacket.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ClientPacket.kt index e1d671231..d0d048630 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ClientPacket.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ClientPacket.kt @@ -1,3 +1,5 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") + package net.mamoe.mirai.network.packet import lombok.Getter @@ -12,12 +14,12 @@ import java.security.MessageDigest /** * @author Him188moe */ -@ExperimentalUnsignedTypes + abstract class ClientPacket : ByteArrayDataOutputStream(), Packet { @Getter val idHex: String - var encoded: Boolean = false + private var encoded: Boolean = false init { val annotation = this.javaClass.getAnnotation(PacketId::class.java) @@ -84,7 +86,6 @@ abstract class ClientPacket : ByteArrayDataOutputStream(), Packet { } -@ExperimentalUnsignedTypes @Throws(IOException::class) fun DataOutputStream.writeIP(ip: String) { for (s in ip.trim().split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) { @@ -97,14 +98,13 @@ fun DataOutputStream.writeTime() { this.writeInt(System.currentTimeMillis().toInt()) } -@ExperimentalUnsignedTypes @Throws(IOException::class) -fun DataOutputStream.writeHex(hex: String) { - for (s in hex.trim().split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) { +fun DataOutputStream.writeHex(uHex: String) { + for (s in uHex.trim().split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) { if (s.isEmpty()) { continue } - this.writeByte(s.toUByte(16).toByte().toInt()) + this.writeByte(s.toUByte(16).toInt()) } } @@ -113,15 +113,13 @@ fun DataOutputStream.encryptAndWrite(byteArray: ByteArray, key: ByteArray) { } fun DataOutputStream.encryptAndWrite(key: ByteArray, encoder: (ByteArrayDataOutputStream) -> Unit) { - this.write(TEA.encrypt(ByteArrayDataOutputStream().let { encoder(it); it.toByteArray() }, key)) + this.write(TEA.encrypt(ByteArrayDataOutputStream().also(encoder).toByteArray(), key)) } -@ExperimentalUnsignedTypes fun DataOutputStream.encryptAndWrite(keyHex: String, encoder: (ByteArrayDataOutputStream) -> Unit) { this.encryptAndWrite(keyHex.hexToBytes(), encoder) } -@ExperimentalUnsignedTypes @Throws(IOException::class) fun DataOutputStream.writeTLV0006(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray) { ByteArrayDataOutputStream().let { @@ -146,12 +144,10 @@ fun DataOutputStream.writeTLV0006(qq: Long, password: String, loginTime: Int, lo } } -@ExperimentalUnsignedTypes -@TestedSuccessfully +@Tested fun DataOutputStream.writeCRC32() = writeCRC32(getRandomByteArray(16)) -@ExperimentalUnsignedTypes fun DataOutputStream.writeCRC32(key: ByteArray) { key.let { write(it)//key @@ -159,8 +155,8 @@ fun DataOutputStream.writeCRC32(key: ByteArray) { } } -@ExperimentalUnsignedTypes -@TestedSuccessfully + +@Tested fun DataOutputStream.writeDeviceName(random: Boolean = false) { val deviceName: String = if (random) { String(getRandomByteArray(10)) @@ -185,7 +181,7 @@ fun Int.toByteArray(): ByteArray = byteArrayOf( /** * 255u -> 00 00 00 FF */ -@ExperimentalUnsignedTypes + fun UInt.toByteArray(): ByteArray = byteArrayOf( (this.shr(24) and 255u).toByte(), (this.shr(16) and 255u).toByte(), @@ -193,24 +189,14 @@ fun UInt.toByteArray(): ByteArray = byteArrayOf( (this.shr(0) and 255u).toByte() ) -/** - * 255 -> FF 00 00 00 - */ -fun Int.toLByteArray(): ByteArray = byteArrayOf( - (this.ushr(0) and 0xFF).toByte(), - (this.ushr(8) and 0xFF).toByte(), - (this.ushr(16) and 0xFF).toByte(), - (this.ushr(24) and 0xFF).toByte() -) -@ExperimentalUnsignedTypes -fun Int.toUHexString(separator: String = " "): String = this.toByteArray().toUByteArray().toUHexString(separator) +fun Int.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator) internal fun md5(str: String): ByteArray = MessageDigest.getInstance("MD5").digest(str.toByteArray()) internal fun md5(byteArray: ByteArray): ByteArray = MessageDigest.getInstance("MD5").digest(byteArray) -@ExperimentalUnsignedTypes + @Throws(IOException::class) fun DataOutputStream.writeZero(count: Int) { repeat(count) { @@ -225,33 +211,26 @@ fun DataOutputStream.writeRandom(length: Int) { } } -@ExperimentalUnsignedTypes + @Throws(IOException::class) fun DataOutputStream.writeQQ(qq: Long) { this.write(qq.toUInt().toByteArray()) } -@ExperimentalUnsignedTypes @Throws(IOException::class) fun DataOutputStream.writeGroup(groupIdOrGroupNumber: Long) { this.write(groupIdOrGroupNumber.toUInt().toByteArray()) } -fun DataOutputStream.writeVarByteArray(byteArray: ByteArray) { +fun DataOutputStream.writeLVByteArray(byteArray: ByteArray) { this.writeShort(byteArray.size) this.write(byteArray) } -fun DataOutputStream.writeVarString(str: String) { - this.writeVarByteArray(str.toByteArray()) +fun DataOutputStream.writeLVString(str: String) { + this.writeLVByteArray(str.toByteArray()) } -fun DataOutputStream.writeVarShort(short: Int) { - this.writeByte(0x02) - this.writeShort(short) -} - -fun DataOutputStream.writeVarInt(int: Int) { - this.writeByte(0x04) - this.writeInt(int) +fun DataOutputStream.writeLVHex(hex: String) { + this.writeLVByteArray(hex.hexToBytes()) } \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/GradeInfo.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/GradeInfo.kt index c208c07f0..d431672f9 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/GradeInfo.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/GradeInfo.kt @@ -9,7 +9,7 @@ import java.io.DataInputStream * * @author Him188moe */ -@ExperimentalUnsignedTypes + @PacketId("00 5C") class ClientAccountInfoRequestPacket( private val qq: Long, diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Heartbeat.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Heartbeat.kt index 9fcd36304..8a1192842 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Heartbeat.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Heartbeat.kt @@ -7,7 +7,7 @@ import java.io.IOException /** * @author Him188moe */ -@ExperimentalUnsignedTypes + @PacketId("00 58") class ClientHeartbeatPacket( private val qq: Long, diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/SKey.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/SKey.kt index 30f8c16b3..a9e61c8ab 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/SKey.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/SKey.kt @@ -6,9 +6,11 @@ import java.io.DataInputStream /** + * SKey 用于 http api + * * @author Him188moe */ -@ExperimentalUnsignedTypes + @PacketId("00 1D") class ClientSKeyRequestPacket( private val qq: Long, @@ -29,7 +31,7 @@ class ClientSKeyRequestPacket( * @author Him188moe */ @PacketId("00 1D") -@ExperimentalUnsignedTypes + class ClientSKeyRefreshmentRequestPacket( private val qq: Long, private val sessionKey: ByteArray diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerEvent.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerEvent.kt index 7bb280023..7b779d718 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerEvent.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerEvent.kt @@ -1,3 +1,5 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE") + package net.mamoe.mirai.network.packet import net.mamoe.mirai.message.FaceID @@ -19,10 +21,10 @@ import java.util.zip.GZIPInputStream * * @author Him188moe */ -open class ServerEventPacket(input: DataInputStream, val packetId: ByteArray, val eventIdentity: ByteArray) : ServerPacket(input) { +abstract class ServerEventPacket(input: DataInputStream, val packetId: ByteArray, val eventIdentity: ByteArray) : ServerPacket(input) { @PacketId("00 17") class Raw(input: DataInputStream, private val packetId: ByteArray) : ServerPacket(input) { - @ExperimentalUnsignedTypes + fun distribute(): ServerEventPacket { val eventIdentity = this.input.readNBytes(16) val type = this.input.goto(18).readNBytes(2) @@ -80,6 +82,7 @@ class ServerGroupUploadFileEventPacket(input: DataInputStream, packetId: ByteArr }//todo test } +@Suppress("EXPERIMENTAL_API_USAGE") class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity) { var groupNumber: Long = 0 var qq: Long = 0 @@ -100,16 +103,17 @@ class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray, OTHER, } - @ExperimentalUnsignedTypes + override fun decode() { + println(this.input.goto(0).readAllBytes().toUHexString()) groupNumber = this.input.goto(51).readInt().toLong() - qq = this.input.goto(56).readLong().toUInt().toLong() + qq = this.input.goto(56).readLong() val fontLength = this.input.goto(108).readShort() //println(this.input.goto(110 + fontLength).readNBytesAt(2).toUHexString())//always 00 00 messageType = when (val id = this.input.goto(110 + fontLength + 2).readByte().toInt()) { 0x13 -> MessageType.NORMAL - 0xE -> MessageType.XML + 0x0E -> MessageType.XML 0x06 -> MessageType.AT @@ -119,7 +123,7 @@ class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray, 0x19 -> MessageType.ANONYMOUS else -> { - MiraiLogger debug ("ServerGroupMessageEventPacket id=$id") + MiraiLogger.debug("ServerGroupMessageEventPacket id=$id") MessageType.OTHER } } @@ -188,7 +192,7 @@ class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray var qq: Long = 0 lateinit var message: MessageChain - @ExperimentalUnsignedTypes + override fun decode() { input.goto(0) println() @@ -222,16 +226,16 @@ class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray //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(readVarNumber().toInt())//可能这个是id, 也可能下面那个 + val id1 = FaceID.ofId(readLVNumber().toInt())//可能这个是id, 也可能下面那个 this.skip(this.readByte().toLong()) - this.readVarNumber()//某id? + this.readLVNumber()//某id? return Face(id1) } 0x06 -> { this.skip(sectionLength - 37 - 1) val imageId = String(this.readNBytes(36)) this.skip(1)//0x41 - return Image(imageId) + return Image("{$imageId}.jpg")//todo 如何确定文件后缀?? } else -> null } @@ -291,8 +295,7 @@ B1 89 BE 09 8F 00 1A E5 00 0B 03 A2 09 90 BB 7A 1F 40 00 A6 00 00 00 20 00 05 00 * 告知服务器已经收到数据 */ @PacketId("")//随后写入 -@ExperimentalUnsignedTypes -class ClientMessageResponsePacket( +class ClientEventResponsePacket( private val qq: Long, private val packetIdFromServer: ByteArray,//4bytes private val sessionKey: ByteArray, @@ -329,7 +332,7 @@ class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray lateinit var message: String - @ExperimentalUnsignedTypes + override fun decode() { //start at Sep1.0:27 qq = input.readIntAt(0) diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerPacket.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerPacket.kt index eaed7769f..580664e0f 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerPacket.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerPacket.kt @@ -1,12 +1,23 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE") + +//to simplify code + package net.mamoe.mirai.network.packet +import net.mamoe.mirai.Bot +import net.mamoe.mirai.event.events.network.ServerPacketReceivedEvent +import net.mamoe.mirai.event.hookWhile import net.mamoe.mirai.network.packet.PacketNameFormatter.adjustName import net.mamoe.mirai.network.packet.action.ServerCanAddFriendResponsePacket import net.mamoe.mirai.network.packet.action.ServerSendFriendMessageResponsePacket import net.mamoe.mirai.network.packet.action.ServerSendGroupMessageResponsePacket +import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageResponsePacket import net.mamoe.mirai.network.packet.login.* +import net.mamoe.mirai.task.MiraiThreadPool import net.mamoe.mirai.utils.* import java.io.DataInputStream +import java.io.EOFException +import kotlin.reflect.KClass /** * @author Him188moe @@ -45,7 +56,6 @@ abstract class ServerPacket(val input: DataInputStream) : Packet { companion object { - @ExperimentalUnsignedTypes fun ofByteArray(bytes: ByteArray): ServerPacket { val stream = bytes.dataInputStream() @@ -72,7 +82,7 @@ abstract class ServerPacket(val input: DataInputStream) : Packet { println(bytes.size) return ServerLoginResponseFailedPacket(when (bytes.size) { - 63, 319, 135, 351 -> LoginState.WRONG_PASSWORD//这四个其中一个也是被冻结 + 63, 319, 135, 351 -> LoginState.WRONG_PASSWORD//这四个其中一个是被冻结 //135 -> LoginState.RETYPE_PASSWORD 279 -> LoginState.BLOCKED 263 -> LoginState.UNKNOWN_QQ_NUMBER @@ -111,6 +121,8 @@ abstract class ServerPacket(val input: DataInputStream) : Packet { "00 A7" -> ServerCanAddFriendResponsePacket(stream) + "03 88" -> ServerTryUploadGroupImageResponsePacket.Encrypted(stream) + else -> throw IllegalArgumentException(idHex) } }.apply { this.idHex = idHex } @@ -118,9 +130,8 @@ abstract class ServerPacket(val input: DataInputStream) : Packet { } - @ExperimentalUnsignedTypes override fun toString(): String { - return adjustName(this.javaClass.simpleName + "(${this.getFixedId()})") + this.getAllDeclaredFields().filterNot { it.name == "idHex" || it.name == "encoded" }.joinToString(", ", "{", "}") { + return adjustName(this.javaClass.simpleName + "(${this.getFixedId()})") + this.getAllDeclaredFields().filterNot { it.name == "idHex" || it.name == "idByteArray" || it.name == "encoded" }.joinToString(", ", "{", "}") { it.trySetAccessible(); it.name + "=" + it.get(this).let { value -> when (value) { is ByteArray -> value.toUHexString() @@ -145,38 +156,37 @@ abstract class ServerPacket(val input: DataInputStream) : Packet { return decryptAsByteArray(key).dataInputStream() } - @ExperimentalUnsignedTypes + fun decryptBy(keyHex: String): DataInputStream { return this.decryptBy(keyHex.hexToBytes()) } fun decryptBy(key1: ByteArray, key2: ByteArray): DataInputStream { - return TEA.decrypt(this.decryptAsByteArray(key1), key2).dataInputStream(); + return TEA.decrypt(this.decryptAsByteArray(key1), key2).dataInputStream() } - @ExperimentalUnsignedTypes + fun decryptBy(key1: String, key2: ByteArray): DataInputStream { return this.decryptBy(key1.hexToBytes(), key2) } - @ExperimentalUnsignedTypes + fun decryptBy(key1: ByteArray, key2: String): DataInputStream { return this.decryptBy(key1, key2.hexToBytes()) } - @ExperimentalUnsignedTypes + fun decryptBy(keyHex1: String, keyHex2: String): DataInputStream { return this.decryptBy(keyHex1.hexToBytes(), keyHex2.hexToBytes()) } - private fun decryptAsByteArray(key: ByteArray): ByteArray { + fun decryptAsByteArray(key: ByteArray): ByteArray { input.goto(14) return TEA.decrypt(input.readAllBytes().cutTail(1), key) } } -@ExperimentalUnsignedTypes fun DataInputStream.readIP(): String { var buff = "" for (i in 0..3) { @@ -207,7 +217,7 @@ fun ByteArray.dataInputStream(): DataInputStream = DataInputStream(this.inputStr */ infix fun DataInputStream.goto(position: N): DataInputStream { this.reset() - this.skip(position.toLong()); + this.skip(position.toLong()) return this } @@ -221,7 +231,7 @@ fun DataInputStream.readNBytes(length: N): ByteArray { } -fun DataInputStream.readVarNumber(): Number { +fun DataInputStream.readLVNumber(): Number { return when (this.readShort().toInt()) { 1 -> this.readByte() 2 -> this.readShort() @@ -238,23 +248,92 @@ fun DataInputStream.readNBytesIn(range: IntRange): ByteArray { fun DataInputStream.readIntAt(position: N): Int { this.goto(position) - return this.readInt(); + return this.readInt() } -@ExperimentalUnsignedTypes + fun DataInputStream.readUIntAt(position: N): UInt { this.goto(position) - return this.readNBytes(4).toUInt(); + return this.readNBytes(4).toUInt() } fun DataInputStream.readByteAt(position: N): Byte { this.goto(position) - return this.readByte(); + return this.readByte() } fun DataInputStream.readShortAt(position: N): Short { this.goto(position) - return this.readShort(); + return this.readShort() } + +@JvmSynthetic +fun DataInputStream.gotoWhere(matcher: UByteArray): DataInputStream { + return this.gotoWhere(matcher.toByteArray()) +} + +/** + * 去往下一个含这些连续字节的位置 + */ +@Throws(EOFException::class) +fun DataInputStream.gotoWhere(matcher: ByteArray): DataInputStream { + require(matcher.isNotEmpty()) + + loop@ + do { + val byte = this.readByte() + if (byte == matcher[0]) { + //todo mark here + for (i in 1 until matcher.size) { + val b = this.readByte() + if (b != matcher[i]) { + continue@loop //todo goto mark + } + return this + } + } + } while (true) +} + + +@Suppress("UNCHECKED_CAST") +internal fun

Bot.waitForPacket(packetClass: KClass

, timeoutMillis: Long = Long.MAX_VALUE, timeout: () -> Unit = {}) { + var got = false + ServerPacketReceivedEvent::class.hookWhile { + if (packetClass.isInstance(it.packet) && it.bot === this) { + got = true + true + } else { + false + } + } + + + MiraiThreadPool.getInstance().submit { + val startingTime = System.currentTimeMillis() + while (!got) { + if (System.currentTimeMillis() - startingTime > timeoutMillis) { + timeout.invoke() + return@submit + } + Thread.sleep(10) + } + } +} + +/* +@Throws(EOFException::class) +fun DataInputStream.gotoWhere(matcher: ByteArray) { + require(matcher.isNotEmpty()) + do { + val byte = this.readByte() + if (byte == matcher[0]) { + for (i in 1 until matcher.size){ + + } + } + } while (true) +}*/ + fun ByteArray.cutTail(length: Int): ByteArray = this.copyOfRange(0, this.size - length) \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Session.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Session.kt index 2484adfb8..f51c2d824 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Session.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Session.kt @@ -1,9 +1,7 @@ package net.mamoe.mirai.network.packet import net.mamoe.mirai.network.Protocol -import net.mamoe.mirai.utils.ByteArrayDataOutputStream import net.mamoe.mirai.utils.TEA -import net.mamoe.mirai.utils.getRandomByteArray import net.mamoe.mirai.utils.lazyEncode import java.io.DataInputStream import java.net.InetAddress @@ -11,64 +9,67 @@ import java.net.InetAddress /** * @author Him188moe */ -@ExperimentalUnsignedTypes + @PacketId("08 28 04 34") class ClientSessionRequestPacket( private val qq: Long, private val serverIp: String, private val token38: ByteArray, private val token88: ByteArray, - private val encryptionKey: ByteArray, - private val tlv0105: ByteArray + private val encryptionKey: ByteArray ) : ClientPacket() { override fun encode() { this.writeQQ(qq) this.writeHex("02 00 00 00 01 2E 01 00 00 68 52 00 30 00 3A") this.writeHex("00 38") this.write(token38) - this.write(TEA.encrypt(object : ByteArrayDataOutputStream() { - override fun toByteArray(): ByteArray { - this.writeHex("00 07 00 88") - this.write(token88) - this.writeHex("00 0C 00 16 00 02 00 00 00 00 00 00 00 00 00 00") - this.writeIP(serverIp) - this.writeHex("1F 40 00 00 00 00 00 15 00 30 00 01")//fix1 - this.writeHex("01 92 A5 D2 59 00 10 54 2D CF 9B 60 BF BB EC 0D D4 81 CE 36 87 DE 35 02 AE 6D ED DC 00 10 ") - this.writeHex(Protocol.fix0836) - this.writeHex("00 36 00 12 00 02 00 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00") - this.writeHex(Protocol.constantData1) - this.writeHex(Protocol.constantData2) - this.writeQQ(qq) - this.writeHex("00 00 00 00 00 1F 00 22 00 01") - this.writeHex("1A 68 73 66 E4 BA 79 92 CC C2 D4 EC 14 7C 8B AF 43 B0 62 FB 65 58 A9 EB 37 55 1D 26 13 A8 E5 3D")//device ID - this.write(tlv0105) - this.writeHex("01 0B 00 85 00 02") - this.writeHex("B9 ED EF D7 CD E5 47 96 7A B5 28 34 CA 93 6B 5C")//fix2 - this.write(getRandomByteArray(1)) - this.writeHex("10 00 00 00 00 00 00 00 02") + this.encryptAndWrite(encryptionKey) { + it.writeHex("00 07 00 88") + it.write(token88) + it.writeHex("00 0C 00 16 00 02 00 00 00 00 00 00 00 00 00 00") + it.writeIP(serverIp) + it.writeHex("1F 40 00 00 00 00 00 15 00 30 00 01")//fix1 + it.writeHex("01 92 A5 D2 59 00 10 54 2D CF 9B 60 BF BB EC 0D D4 81 CE 36 87 DE 35 02 AE 6D ED DC 00 10 ") + it.writeHex(Protocol.fix0836) + it.writeHex("00 36 00 12 00 02 00 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00") + it.writeHex(Protocol.constantData1) + it.writeHex(Protocol.constantData2) + it.writeQQ(qq) + it.writeHex("00 00 00 00 00 1F 00 22 00 01") + it.writeHex("1A 68 73 66 E4 BA 79 92 CC C2 D4 EC 14 7C 8B AF 43 B0 62 FB 65 58 A9 EB 37 55 1D 26 13 A8 E5 3D")//device ID - //fix3 - this.writeHex("00 63 3E 00 63 02 04 03 06 02 00 04 00 52 D9 00 00 00 00 A9 58 3E 6D 6D 49 AA F6 A6 D9 33 0A E7 7E 36 84 03 01 00 00 68 20 15 8B 00 00 01 02 00 00 03 00 07 DF 00 0A 00 0C 00 01 00 04 00 03 00 04 20 5C 00") - this.write(getRandomByteArray(32))//md5 32 - this.writeHex("68") + //tlv0106 + it.writeHex("01 05 00 30") + it.writeHex("00 01 01 02 00 14 01 01 00 10") + it.writeRandom(16) + it.writeHex("00 14 01 02 00 10") + it.writeRandom(16) - this.writeHex("00 00 00 00 00 2D 00 06 00 01") - this.writeIP(InetAddress.getLocalHost().hostAddress) + it.writeHex("01 0B 00 85 00 02") + it.writeHex("B9 ED EF D7 CD E5 47 96 7A B5 28 34 CA 93 6B 5C")//fix2 + it.writeRandom(1) + it.writeHex("10 00 00 00 00 00 00 00 02") - return super.toByteArray() - } - }.toByteArray(), encryptionKey)) + //fix3 + it.writeHex("00 63 3E 00 63 02 04 03 06 02 00 04 00 52 D9 00 00 00 00 A9 58 3E 6D 6D 49 AA F6 A6 D9 33 0A E7 7E 36 84 03 01 00 00 68 20 15 8B 00 00 01 02 00 00 03 00 07 DF 00 0A 00 0C 00 01 00 04 00 03 00 04 20 5C 00") + it.writeRandom(32)//md5 32 + it.writeHex("68") + + it.writeHex("00 00 00 00 00 2D 00 06 00 01") + it.writeIP(InetAddress.getLocalHost().hostAddress) + } } } /** * @author Him188moe */ +@PacketId("08 28 04 34") class ServerSessionKeyResponsePacket(inputStream: DataInputStream, private val dataLength: Int) : ServerPacket(inputStream) { lateinit var sessionKey: ByteArray lateinit var tlv0105: ByteArray - @ExperimentalUnsignedTypes + override fun decode() { when (dataLength) { 407 -> { diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Touch.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Touch.kt index c201eac7b..1b9945670 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Touch.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Touch.kt @@ -1,6 +1,7 @@ package net.mamoe.mirai.network.packet import net.mamoe.mirai.network.Protocol +import net.mamoe.mirai.network.packet.login.ClientPasswordSubmissionPacket import net.mamoe.mirai.utils.ByteArrayDataOutputStream import net.mamoe.mirai.utils.TEA import net.mamoe.mirai.utils.hexToBytes @@ -11,11 +12,12 @@ import java.io.IOException /** * A packet received when logging in, used to redirect server address * - * @see net.mamoe.mirai.network.packet.client.login.ClientServerRedirectionPacket - * @see net.mamoe.mirai.network.packet.client.login.ClientPasswordSubmissionPacket + * @see ClientServerRedirectionPacket + * @see ClientPasswordSubmissionPacket * * @author Him188moe */ +@Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @PacketId("08 25 31 01") class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inputStream) { var serverIP: String? = null @@ -29,7 +31,7 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp TYPE_08_25_31_02, } - @ExperimentalUnsignedTypes + override fun decode() { when (val id = input.readByte().toUByte().toInt()) { 0xFE -> { @@ -52,7 +54,7 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp } class Encrypted(private val type: Type, inputStream: DataInputStream) : ServerPacket(inputStream) { - @ExperimentalUnsignedTypes + fun decrypt(): ServerTouchResponsePacket = ServerTouchResponsePacket(decryptBy(when (type) { Type.TYPE_08_25_31_02 -> Protocol.redirectionKey.hexToBytes() Type.TYPE_08_25_31_01 -> Protocol.key0825.hexToBytes() @@ -65,10 +67,10 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp * * @author Him188moe */ -@ExperimentalUnsignedTypes + @PacketId("08 25 31 01") class ClientTouchPacket(private val qq: Long, private val serverIp: String) : ClientPacket() { - @ExperimentalUnsignedTypes + @Throws(IOException::class) override fun encode() { this.writeQQ(qq) @@ -80,7 +82,7 @@ class ClientTouchPacket(private val qq: Long, private val serverIp: String) : Cl it.writeHex(Protocol.constantData2) it.writeQQ(qq) it.writeHex("00 00 00 00 03 09 00 08 00 01") - it.writeIP(serverIp); + it.writeIP(serverIp) it.writeHex("00 02 00 36 00 12 00 02 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 14 00 1D 01 02 00 19") it.writeHex(Protocol.publicKey) } @@ -92,10 +94,10 @@ class ClientTouchPacket(private val qq: Long, private val serverIp: String) : Cl * * @author Him188moe */ -@ExperimentalUnsignedTypes + @PacketId("08 25 31 02") class ClientServerRedirectionPacket(private val serverIP: String, private val qq: Long) : ClientPacket() { - @ExperimentalUnsignedTypes + override fun encode() { this.writeQQ(qq) this.writeHex(Protocol.fixVer) diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/VerificationCode.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/VerificationCode.kt index 74e6813a2..484a19e42 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/VerificationCode.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/VerificationCode.kt @@ -1,13 +1,15 @@ package net.mamoe.mirai.network.packet import net.mamoe.mirai.network.Protocol -import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.TEA +import net.mamoe.mirai.utils.Tested +import net.mamoe.mirai.utils.hexToBytes import java.io.DataInputStream /** * 客户端请求验证码图片数据的第几部分 */ -@ExperimentalUnsignedTypes + @PacketId("00 BA 31") class ClientVerificationCodeTransmissionRequestPacket( private val packetId: Int, @@ -16,11 +18,8 @@ class ClientVerificationCodeTransmissionRequestPacket( private val verificationSequence: Int, private val token00BA: ByteArray ) : ClientPacket() { - @TestedSuccessfully + @Tested override fun encode() { - MiraiLogger debug "packetId=$packetId" - MiraiLogger debug "verificationSequence=$verificationSequence" - this.writeByte(packetId)//part of packet id this.writeQQ(qq) @@ -47,7 +46,7 @@ class ClientVerificationCodeTransmissionRequestPacket( * 提交验证码 */ @PacketId("00 BA 32") -@ExperimentalUnsignedTypes + class ClientVerificationCodeSubmitPacket( private val packetIdLast: Int, private val qq: Long, @@ -90,39 +89,11 @@ class ClientVerificationCodeSubmitPacket( } } -@ExperimentalUnsignedTypes -fun main() { - val token0825 = "6E AF F9 2C 20 2B DE 21 B6 13 6F 26 43 F4 04 7B 1F 88 08 4E 8E BE E5 D1 3F E7 93 DE DD E0 6E 38 65 C7 C7 D3 20 7D AC 73 AD F9 85 F9 CC 2A 2C 26 C6 B1 5B FD 34 3F D4 F2".hexToBytes() - val verificationCode = "AAAA" - val verificationToken = "84 2D 1D 9D 07 04 34 80 17 9E 3F 58 02 20 9A 1C 22 D0 73 7D 8A 90 1B 2F F8 E6 79 A6 84 2F 98 F5 1E 66 3D 9A 24 59 18 34 42 BD 45 DA E1 22 2D BC 2D 36 80 86 AD 44 C2 94".hexToBytes() -//00 02 00 00 08 04 01 E0 00 00 04 53 00 00 00 01 00 00 15 85 01 00 38 6E AF F9 2C 20 2B DE 21 B6 13 6F 26 43 F4 04 7B 1F 88 08 4E 8E BE E5 D1 3F E7 93 DE DD E0 6E 38 65 C7 C7 D3 20 7D AC 73 AD F9 85 F9 CC 2A 2C 26 C6 B1 5B FD 34 3F D4 F2 01 03 00 19 02 6D 28 41 D2 A5 6F D2 FC 3E 2A 1F 03 75 DE 6E 28 8F A8 19 3E 5F 16 49 D3 14 00 05 00 00 00 00 00 04 58 51 4E 44 00 38 84 2D 1D 9D 07 04 34 80 17 9E 3F 58 02 20 9A 1C 22 D0 73 7D 8A 90 1B 2F F8 E6 79 A6 84 2F 98 F5 1E 66 3D 9A 24 59 18 34 42 BD 45 DA E1 22 2D BC 2D 36 80 86 AD 44 C2 94 00 10 69 20 D1 14 74 F5 B3 93 E4 D5 02 B3 71 1A CD 2A - ByteArrayDataOutputStream().let { - it.writeHex("00 02 00 00 08 04 01 E0") - it.writeHex(Protocol.constantData2) - it.writeHex("01 00 38") - it.write(token0825) - it.writeHex("01 03") - - it.writeShort(25) - it.writeHex(Protocol.publicKey) - - it.writeHex("14 00 05 00 00 00 00 00 04") - it.write(verificationCode.substring(0..3).toByteArray()) - it.writeHex("00 38") - it.write(verificationToken) - - it.writeHex("00 10") - it.writeHex(Protocol.key00BAFix) - - println(it.toByteArray().toUHexString()) - } -} - /** * 刷新验证码 */ @PacketId("00 BA 31") -@ExperimentalUnsignedTypes + class ClientVerificationCodeRefreshPacket( private val packetIdLast: Int, private val qq: Long, @@ -152,7 +123,7 @@ class ClientVerificationCodeRefreshPacket( } /** - * 验证码输入错误 + * 验证码输入错误, 同时也会给一部分验证码 */ @PacketId("00 BA 32") class ServerVerificationCodeWrongPacket(input: DataInputStream, dataSize: Int, packetId: ByteArray) : ServerVerificationCodeTransmissionPacket(input, dataSize, packetId) @@ -171,7 +142,7 @@ open class ServerVerificationCodeTransmissionPacket(input: DataInputStream, priv lateinit var token00BA: ByteArray//40 bytes var packetIdLast: Int = 0 - @ExperimentalUnsignedTypes + override fun decode() { this.verificationToken = this.input.readNBytesAt(10, 56) @@ -210,7 +181,7 @@ class ServerVerificationCodeCorrectPacket(input: DataInputStream) : ServerVerifi lateinit var token00BA: ByteArray//56 bytes - @ExperimentalUnsignedTypes + override fun decode() { token00BA = this.input.readNBytesAt(10, 56) } @@ -220,7 +191,7 @@ abstract class ServerVerificationCodePacket(input: DataInputStream) : ServerPack @PacketId("00 BA") class Encrypted(input: DataInputStream, private val id: String) : ServerPacket(input) { - @ExperimentalUnsignedTypes + fun decrypt(): ServerVerificationCodePacket { this.input goto 14 val data = TEA.decrypt(this.input.readAllBytes().cutTail(1), Protocol.key00BA.hexToBytes()) diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/AddContact.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/AddContact.kt index 2508db00a..c19724dba 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/AddContact.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/AddContact.kt @@ -1,3 +1,5 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") + package net.mamoe.mirai.network.packet.action import net.mamoe.mirai.network.Protocol @@ -13,7 +15,7 @@ import java.util.* * @author Him188moe */ @PacketId("00 A7") -@ExperimentalUnsignedTypes + class ClientCanAddFriendPacket( val bot: Long, val qq: Long, @@ -47,7 +49,7 @@ class ServerCanAddFriendResponsePacket(input: DataInputStream) : ServerPacket(in FAILED, } - @ExperimentalUnsignedTypes + override fun decode() { val data = input.goto(0).readAllBytes() if (data.size == 99) { @@ -78,7 +80,7 @@ class ServerCanAddFriendResponsePacket(input: DataInputStream) : ServerPacket(in * 请求添加好友 */ @PacketId("00 AE") -@ExperimentalUnsignedTypes + class ClientAddFriendPacket( val bot: Long, val qq: Long, @@ -111,7 +113,7 @@ class ServerAddGroupResponsePacket(input: DataInputStream) : ServerAddContactRes /** * 添加好友/群的回复 */ -open class ServerAddContactResponsePacket(input: DataInputStream) : ServerPacket(input) { +abstract class ServerAddContactResponsePacket(input: DataInputStream) : ServerPacket(input) { class Raw(input: DataInputStream) : ServerPacket(input) { diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/ClientSendFriendMessagePacket.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/ClientSendFriendMessagePacket.kt index 509726ffd..d60aeacb1 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/ClientSendFriendMessagePacket.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/ClientSendFriendMessagePacket.kt @@ -10,7 +10,7 @@ import java.io.DataInputStream * @author Him188moe */ @PacketId("00 CD") -@ExperimentalUnsignedTypes + class ClientSendFriendMessagePacket( private val botQQ: Long, private val targetQQ: Long, @@ -19,6 +19,7 @@ class ClientSendFriendMessagePacket( ) : ClientPacket() { override fun encode() { this.writeRandom(2)//part of packet id + this.writeQQ(botQQ) this.writeHex(Protocol.fixVer2) @@ -40,7 +41,6 @@ class ClientSendFriendMessagePacket( it.writeHex(Protocol.friendMessageConst1)//... 85 E9 BB 91 it.writeZero(2) - it.write(message.toByteArray()) /* diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/ClientSendGroupMessagePacket.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/ClientSendGroupMessagePacket.kt index 17f84f988..bc480103d 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/ClientSendGroupMessagePacket.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/ClientSendGroupMessagePacket.kt @@ -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.toUHexString @@ -9,12 +10,11 @@ import java.io.DataInputStream * @author Him188moe */ @PacketId("00 02") -@ExperimentalUnsignedTypes class ClientSendGroupMessagePacket( private val groupId: Long,//不是 number private val botQQ: Long, private val sessionKey: ByteArray, - private val message: String + private val message: MessageChain ) : ClientPacket() { override fun encode() { this.writeRandom(2)//part of packet id @@ -25,20 +25,24 @@ class ClientSendGroupMessagePacket( val bytes = message.toByteArray() it.writeByte(0x2A) it.writeGroup(groupId) - it.writeShort(56 + bytes.size) - it.writeHex("00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00") + it.writeShort(50 + bytes.size) + it.writeHex("00 01 01") + it.writeHex("00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00") + it.writeTime() it.writeRandom(4) - it.writeHex("Protocol.messageConst1") + it.writeHex("00 00 00 00 09 00 86") + it.writeHex(Protocol.friendMessageConst1) it.writeZero(2) //messages - it.writeByte(0x01) + it.write(bytes) + /*it.writeByte(0x01) it.writeShort(bytes.size + 3) it.writeByte(0x01) it.writeShort(bytes.size) - it.write(bytes) + it.write(bytes)*/ println(it.toByteArray().toUHexString()) } diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/image/ClientGetGroupImageIDPacket.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/image/ClientGetGroupImageIDPacket.kt deleted file mode 100644 index fec7478db..000000000 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/image/ClientGetGroupImageIDPacket.kt +++ /dev/null @@ -1,35 +0,0 @@ -package net.mamoe.mirai.network.packet.image - -import net.mamoe.mirai.network.packet.* -import net.mamoe.mirai.utils.writeProtoInt -import java.awt.image.BufferedImage - -/** - * 查询群消息的 image id. - * That is, 查询服务器上是否有这个图片, 有就返回 id, 没有就需要上传 - * - * @author Him188moe - */ -@PacketId("03 88") -@ExperimentalUnsignedTypes -class ClientGetGroupImageIDPacket( - val bot: Long, - val sessionKey: ByteArray, - val group: Long, - val image: BufferedImage -) : ClientPacket() { - override fun encode() { - this.writeRandom(2) - - this.writeQQ(bot) - this.writeHex("04 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 00 00 00") - this.encryptAndWrite(sessionKey) { - it.writeHex("00 00 00 07 00 00 00 5E 08 01 12 03 98 01 01 10 01 1A") - it.writeHex("5A") - it.writeHex("08") - it.writeProtoInt(group) - it.writeHex("08") - it.writeProtoInt(image.height) - } - } -} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/image/UploadGroupImage.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/image/UploadGroupImage.kt new file mode 100644 index 000000000..839f9c8a9 --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/image/UploadGroupImage.kt @@ -0,0 +1,132 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") + +package net.mamoe.mirai.network.packet.image + +import net.mamoe.mirai.network.packet.* +import net.mamoe.mirai.utils.toByteArray +import net.mamoe.mirai.utils.writeUVarInt +import java.awt.image.BufferedImage +import java.io.DataInputStream + +/** + * 请求上传图片. 将发送图片的 md5, size. + * 服务器返回以下之一: + * - 服务器已经存有这个图片 [ServerTryUploadGroupImageFailedPacket] + * - 服务器未存有, 返回一个 key 用于客户端上传 [ServerTryUploadGroupImageSuccessPacket] + * + * @author Him188moe + */ +@PacketId("03 88") +class ClientTryGetImageIDPacket( + private val botNumber: Long, + private val sessionKey: ByteArray, + private val groupNumberOrQQNumber: Long,//todo 为什么还要有qq number呢? bot不就是了么 + private val image: BufferedImage +) : ClientPacket() { + override fun encode() { + this.writeRandom(2) + + this.writeQQ(botNumber) + this.writeHex("04 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 00 00 00") + + val byteArray = image.toByteArray() + this.encryptAndWrite(sessionKey) { + it.writeZero(3) + + it.writeHex("07 00") + + it.writeZero(2) + + it.writeHex("5E") + it.writeHex("08") + it.writeHex("01 12 03 98 01 01 10 01") + + it.writeHex("1A") + it.writeHex("5A") + + it.writeHex("08") + it.writeUVarInt(groupNumberOrQQNumber) + + it.writeHex("10") + it.writeUVarInt(botNumber) + + it.writeHex("18 00") + + it.writeHex("22") + it.writeHex("10") + it.write(md5(byteArray)) + + it.writeHex("28") + it.writeUVarInt(byteArray.size.toUInt()) + + it.writeHex("32") + it.writeHex("1A") + it.writeHex("37 00 4D 00 32 00 25 00 4C 00 31 00 56 00 32 00 7B 00 39 00 30 00 29 00 52 00") + + it.writeHex("38 01") + + it.writeHex("48 01") + + it.writeHex("50") + it.writeUVarInt(image.width.toUInt()) + it.writeHex("58") + it.writeUVarInt(image.height.toUInt()) + + it.writeHex("60 04") + + it.writeHex("6A") + it.writeHex("05") + it.writeHex("32 36 36 35 36") + + it.writeHex("70 00") + + it.writeHex("78 03") + + it.writeHex("80 01") + + it.writeHex("00") + } + } +} + +abstract class ServerTryUploadGroupImageResponsePacket(input: DataInputStream) : ServerPacket(input) { + + class Encrypted(input: DataInputStream) : ServerPacket(input) { + fun decrypt(sessionKey: ByteArray): ServerTryUploadGroupImageResponsePacket { + val data = this.decryptAsByteArray(sessionKey) + println(data.size) + println(data.size) + if (data.size == 209) { + return ServerTryUploadGroupImageSuccessPacket(data.dataInputStream()).setId(this.idHex) + } + + return ServerTryUploadGroupImageFailedPacket(data.dataInputStream()) + } + } +} + +/** + * 服务器未存有图片, 返回一个 key 用于客户端上传 + */ +class ServerTryUploadGroupImageSuccessPacket(input: DataInputStream) : ServerTryUploadGroupImageResponsePacket(input) { + lateinit var uKey: ByteArray + + + override fun decode() { + uKey = this.input.gotoWhere(ubyteArrayOf(0x42u, 0x80u, 0x01u)).readNBytes(128) + } +} + +/** + * 服务器已经存有这个图片 + */ +class ServerTryUploadGroupImageFailedPacket(input: DataInputStream) : ServerTryUploadGroupImageResponsePacket(input) { + override fun decode() { + + } +} + +fun main() { + + println(0xff) +} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ClientChangeOnlineStatusPacket.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ClientChangeOnlineStatusPacket.kt index d817bce50..3d48d48e6 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ClientChangeOnlineStatusPacket.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ClientChangeOnlineStatusPacket.kt @@ -9,7 +9,7 @@ import net.mamoe.mirai.utils.ClientLoginStatus * * @author Him188moe */ -@ExperimentalUnsignedTypes + @PacketId("00 EC") class ClientChangeOnlineStatusPacket( private val qq: Long, @@ -29,4 +29,6 @@ class ClientChangeOnlineStatusPacket( it.writeHex("00 01 00 01 00 04 00 00 00 00") } } -} \ No newline at end of file +} + + diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ClientLogin.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ClientLogin.kt index ccd4edbe1..cb6807a56 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ClientLogin.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ClientLogin.kt @@ -4,7 +4,7 @@ import net.mamoe.mirai.network.Protocol import net.mamoe.mirai.network.packet.* import net.mamoe.mirai.utils.ByteArrayDataOutputStream import net.mamoe.mirai.utils.TEA -import net.mamoe.mirai.utils.TestedSuccessfully +import net.mamoe.mirai.utils.Tested import net.mamoe.mirai.utils.hexToBytes import java.io.DataOutputStream @@ -14,8 +14,8 @@ import java.io.DataOutputStream * @author Him188moe */ @PacketId("08 36 31 03") -@ExperimentalUnsignedTypes -@TestedSuccessfully + +@Tested class ClientPasswordSubmissionPacket( private val qq: Long, private val password: String, @@ -24,7 +24,7 @@ class ClientPasswordSubmissionPacket( private val tgtgtKey: ByteArray, private val token0825: ByteArray ) : ClientPacket() { - @ExperimentalUnsignedTypes + override fun encode() { this.writeQQ(qq) this.writeHex(Protocol.passwordSubmissionKey1) @@ -40,21 +40,21 @@ class ClientPasswordSubmissionPacket( } @PacketId("08 36 31 04") -@ExperimentalUnsignedTypes + class ClientLoginResendPacket3104(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv0006: ByteArray? = null) : ClientLoginResendPacket(qq, password, loginTime, loginIP, tgtgtKey, token0825, token00BA, tlv0006) @PacketId("08 36 31 05") -@ExperimentalUnsignedTypes + class ClientLoginResendPacket3105(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, token00BA: ByteArray) : ClientLoginResendPacket(qq, password, loginTime, loginIP, tgtgtKey, token0825, token00BA, null) @PacketId("08 36 31 06") -@ExperimentalUnsignedTypes + class ClientLoginResendPacket3106(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv0006: ByteArray? = null) : ClientLoginResendPacket(qq, password, loginTime, loginIP, tgtgtKey, token0825, token00BA, tlv0006) -@ExperimentalUnsignedTypes + open class ClientLoginResendPacket internal constructor( val qq: Long, val password: String, @@ -93,7 +93,7 @@ open class ClientLoginResendPacket internal constructor( /** * @author Him188moe */ -@ExperimentalUnsignedTypes + private fun DataOutputStream.writePart1(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, tlv0006: ByteArray? = null) { //this.writeInt(System.currentTimeMillis().toInt()) @@ -130,7 +130,7 @@ private fun DataOutputStream.writePart1(qq: Long, password: String, loginTime: I this.writeHex("60 C9 5D A7 45 70 04 7F 21 7D 84 50 5C 66 A5 C6")//key } -@ExperimentalUnsignedTypes + private fun DataOutputStream.writePart2() { this.writeHex("03 12")//tag diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/LoginState.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/LoginState.kt index 5f3f8b66f..f5ccba7d6 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/LoginState.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/LoginState.kt @@ -34,11 +34,6 @@ enum class LoginState { */ TAKEN_BACK, - /** - * 需要验证码登录 - */ - VERIFICATION_CODE, - /** * 未知. 更换服务器或等几分钟再登录可能解决. */ diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ServerLoginResponseKeyExchangePacket.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ServerLoginResponseKeyExchangePacket.kt index 9470bc0b6..e92a3828e 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ServerLoginResponseKeyExchangePacket.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ServerLoginResponseKeyExchangePacket.kt @@ -4,7 +4,7 @@ import net.mamoe.mirai.network.Protocol import net.mamoe.mirai.network.packet.PacketId import net.mamoe.mirai.network.packet.ServerPacket import net.mamoe.mirai.network.packet.goto -import net.mamoe.mirai.utils.TestedSuccessfully +import net.mamoe.mirai.utils.Tested import java.io.DataInputStream /** @@ -23,7 +23,7 @@ class ServerLoginResponseKeyExchangePacket(input: DataInputStream, val flag: Fla var tokenUnknown: ByteArray? = null lateinit var tgtgtKey: ByteArray//16bytes - @TestedSuccessfully + @Tested override fun decode() { this.input.skip(5) tgtgtKey = this.input.readNBytes(16)//22 @@ -46,8 +46,8 @@ class ServerLoginResponseKeyExchangePacket(input: DataInputStream, val flag: Fla } class Encrypted(input: DataInputStream, private val flag: Flag) : ServerPacket(input) { - @ExperimentalUnsignedTypes - @TestedSuccessfully + + @Tested fun decrypt(tgtgtKey: ByteArray): ServerLoginResponseKeyExchangePacket { return ServerLoginResponseKeyExchangePacket(this.decryptBy(Protocol.shareKey, tgtgtKey), flag).setId(this.idHex) } diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ServerLoginResponsePasswordVerifiedPacket.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ServerLoginResponsePasswordVerifiedPacket.kt index 438b1259d..8bcbe894f 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ServerLoginResponsePasswordVerifiedPacket.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ServerLoginResponsePasswordVerifiedPacket.kt @@ -1,3 +1,5 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE") + package net.mamoe.mirai.network.packet.login import net.mamoe.mirai.network.Protocol @@ -5,7 +7,7 @@ import net.mamoe.mirai.network.packet.ServerPacket import net.mamoe.mirai.network.packet.goto import net.mamoe.mirai.network.packet.readNBytesAt import net.mamoe.mirai.network.packet.readString -import net.mamoe.mirai.utils.TestedSuccessfully +import net.mamoe.mirai.utils.Tested import net.mamoe.mirai.utils.toUHexString import java.io.DataInputStream @@ -21,8 +23,8 @@ class ServerLoginResponseSuccessPacket(input: DataInputStream) : ServerPacket(in lateinit var encryptionKey: ByteArray - @TestedSuccessfully - @ExperimentalUnsignedTypes + @Tested + override fun decode() { this.input.skip(7)//8 this.encryptionKey = this.input.readNBytes(16)//24 @@ -52,7 +54,7 @@ class ServerLoginResponseSuccessPacket(input: DataInputStream) : ServerPacket(in class Encrypted(input: DataInputStream) : ServerPacket(input) { - @ExperimentalUnsignedTypes + fun decrypt(tgtgtKey: ByteArray): ServerLoginResponseSuccessPacket { input goto 14 return ServerLoginResponseSuccessPacket(this.decryptBy(Protocol.shareKey, tgtgtKey)).setId(this.idHex) diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ServerLoginResponseVerificationCodeInitPacket.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ServerLoginResponseVerificationCodeInitPacket.kt index 9625e38b1..314391011 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ServerLoginResponseVerificationCodeInitPacket.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ServerLoginResponseVerificationCodeInitPacket.kt @@ -1,10 +1,12 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE") + package net.mamoe.mirai.network.packet.login import net.mamoe.mirai.network.Protocol import net.mamoe.mirai.network.packet.ServerPacket import net.mamoe.mirai.network.packet.dataInputStream import net.mamoe.mirai.network.packet.goto -import net.mamoe.mirai.utils.TestedSuccessfully +import net.mamoe.mirai.utils.Tested import net.mamoe.mirai.utils.hexToUBytes import java.io.DataInputStream @@ -20,8 +22,8 @@ class ServerLoginResponseVerificationCodeInitPacket(input: DataInputStream, priv var unknownBoolean: Boolean? = null - @TestedSuccessfully - @ExperimentalUnsignedTypes + @Tested + override fun decode() { val verifyCodeLength = this.input.goto(78).readShort()//2bytes this.verifyCodePart1 = this.input.readNBytes(verifyCodeLength.toInt()) @@ -39,7 +41,7 @@ class ServerLoginResponseVerificationCodeInitPacket(input: DataInputStream, priv } - @ExperimentalUnsignedTypes + fun decrypt(): ServerLoginResponseVerificationCodeInitPacket { this.input goto 14 val data = this.decryptBy(Protocol.shareKey).goto(0).readAllBytes() diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ServerLoginSuccessPacket.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ServerLoginSuccessPacket.kt index 4b5d092d0..e7c916e5c 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ServerLoginSuccessPacket.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ServerLoginSuccessPacket.kt @@ -1,5 +1,6 @@ package net.mamoe.mirai.network.packet.login +import net.mamoe.mirai.network.packet.PacketId import net.mamoe.mirai.network.packet.ServerPacket import java.io.DataInputStream @@ -8,4 +9,5 @@ import java.io.DataInputStream * * @author Him188moe */ +@PacketId("00 EC") class ServerLoginSuccessPacket(input: DataInputStream) : ServerPacket(input) \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/utils/ContactList.java b/mirai-core/src/main/java/net/mamoe/mirai/utils/ContactList.java index 6cfde9c52..38c367dcd 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/utils/ContactList.java +++ b/mirai-core/src/main/java/net/mamoe/mirai/utils/ContactList.java @@ -1,10 +1,9 @@ package net.mamoe.mirai.utils; import net.mamoe.mirai.contact.Contact; -import net.mamoe.mirai.utils.config.MiraiSynchronizedLinkedListMap; /** * @author Him188moe */ -public class ContactList extends MiraiSynchronizedLinkedListMap { +public class ContactList extends MiraiSynchronizedLinkedHashMap { } diff --git a/mirai-core/src/main/java/net/mamoe/mirai/utils/ImageNetworkUtils.java b/mirai-core/src/main/java/net/mamoe/mirai/utils/ImageNetworkUtils.java index dbb54d36a..be23a8fcd 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/utils/ImageNetworkUtils.java +++ b/mirai-core/src/main/java/net/mamoe/mirai/utils/ImageNetworkUtils.java @@ -1,36 +1,31 @@ package net.mamoe.mirai.utils; -import org.apache.commons.httpclient.util.HttpURLConnection; -import org.jsoup.Connection; -import org.jsoup.Jsoup; import java.io.IOException; -import java.io.InputStream; +import java.net.HttpURLConnection; import java.net.URL; +/** + * @author NaturalHG + */ public class ImageNetworkUtils { - public static void postImage(String ukey, int fileSize, String g_uin,String groupCode, byte[] img){ + public static boolean postImage(String uKeyHex, int fileSize, long qqNumber, long groupCode, byte[] img) throws IOException { //http://htdata2.qq.com/cgi-bin/httpconn?htcmd=0x6ff0071&ver=5515&term=pc&ukey=” + 删全部空 (ukey) + “&filesize=” + 到文本 (fileSize) + “&range=0&uin=” + g_uin + “&groupcode=” + Group - StringBuilder builder = new StringBuilder("http://htdata2.qq.com/cgi-bin/httpconn?htcmd=0x6ff0071&ver=5515&term=pc"); - builder.append("&ukey=") - .append(ukey.trim()) - .append("&filezise=").append(fileSize) - .append("&range=").append("0") - .append("&uin=").append(g_uin) - .append("&groupcode=").append(groupCode); - try { - HttpURLConnection conn = (HttpURLConnection) new URL(builder.toString()).openConnection(); - conn.setRequestProperty("User-agent","QQClient"); - conn.setRequestProperty("Content-length","" + fileSize); - conn.setRequestMethod("POST"); - conn.getOutputStream().write(img); + String builder = "http://htdata2.qq.com/cgi-bin/httpconn?htcmd=0x6ff0071&ver=5515&term=pc" + "&ukey=" + + uKeyHex.replace(" ", "") + + "&filezise=" + fileSize + + "&range=" + "0" + + "&uin=" + qqNumber + + "&groupcode=" + groupCode; + HttpURLConnection conn = (HttpURLConnection) new URL(builder).openConnection(); + conn.setRequestProperty("User-agent", "QQClient"); + conn.setRequestProperty("Content-length", "" + fileSize); + conn.setRequestMethod("POST"); + conn.setDoOutput(true); + conn.getOutputStream().write(img); - conn.connect(); - System.out.println(conn.getResponseCode()); - - } catch (IOException e) { - e.printStackTrace(); - } + conn.connect(); + return conn.getResponseCode() == 200; } } diff --git a/mirai-core/src/main/java/net/mamoe/mirai/utils/MiraiLogger.kt b/mirai-core/src/main/java/net/mamoe/mirai/utils/MiraiLogger.kt index 349817f16..04b245e70 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/utils/MiraiLogger.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/utils/MiraiLogger.kt @@ -1,6 +1,8 @@ package net.mamoe.mirai.utils import net.mamoe.mirai.Bot +import net.mamoe.mirai.network.packet.ServerPacket +import net.mamoe.mirai.network.packet.goto import java.text.SimpleDateFormat import java.util.* @@ -11,20 +13,20 @@ import java.util.* * @author NaturalHG */ object MiraiLogger { - infix fun log(o: Any?) = info(o) - infix fun println(o: Any?) = info(o) - infix fun info(o: Any?) = this.print(o.toString(), LoggerTextFormat.RESET) + fun log(o: Any?) = info(o) + fun println(o: Any?) = info(o) + fun info(o: Any?) = this.print(o.toString(), LoggerTextFormat.RESET) - infix fun error(o: Any?) = this.print(o.toString(), LoggerTextFormat.RED) + fun error(o: Any?) = this.print(o.toString(), LoggerTextFormat.RED) - infix fun notice(o: Any?) = this.print(o.toString(), LoggerTextFormat.LIGHT_BLUE) + fun notice(o: Any?) = this.print(o.toString(), LoggerTextFormat.LIGHT_BLUE) - infix fun success(o: Any?) = this.print(o.toString(), LoggerTextFormat.GREEN) + fun success(o: Any?) = this.print(o.toString(), LoggerTextFormat.GREEN) - infix fun debug(o: Any?) = this.print(o.toString(), LoggerTextFormat.YELLOW) + fun debug(o: Any?) = this.print(o.toString(), LoggerTextFormat.YELLOW) - infix fun catching(e: Throwable) { + fun catching(e: Throwable) { e.printStackTrace() /* this.print(e.message) @@ -39,21 +41,26 @@ object MiraiLogger { } } -infix fun Bot.log(o: Any?) = info(o) -infix fun Bot.println(o: Any?) = info(o) -infix fun Bot.info(o: Any?) = print(this, o.toString(), LoggerTextFormat.RESET) +fun Bot.log(o: Any?) = info(o) +fun Bot.println(o: Any?) = info(o) +fun Bot.info(o: Any?) = print(this, o.toString(), LoggerTextFormat.RESET) -infix fun Bot.error(o: Any?) = print(this, o.toString(), LoggerTextFormat.RED) +fun Bot.error(o: Any?) = print(this, o.toString(), LoggerTextFormat.RED) -infix fun Bot.notice(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_BLUE) +fun Bot.notice(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_BLUE) -infix fun Bot.purple(o: Any?) = print(this, o.toString(), LoggerTextFormat.PURPLE) +fun Bot.purple(o: Any?) = print(this, o.toString(), LoggerTextFormat.PURPLE) -infix fun Bot.cyanL(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_CYAN) +fun Bot.cyanL(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_CYAN) +fun Bot.success(o: Any?) = print(this, o.toString(), LoggerTextFormat.GREEN) -infix fun Bot.success(o: Any?) = print(this, o.toString(), LoggerTextFormat.GREEN) +fun Bot.debug(o: Any?) = print(this, o.toString(), LoggerTextFormat.YELLOW) -infix fun Bot.debug(o: Any?) = print(this, o.toString(), LoggerTextFormat.YELLOW) +fun Bot.debugPacket(packet: ServerPacket) { + debug("Packet=$packet") + debug("Packet size=" + packet.input.goto(0).readAllBytes().size) + debug("Packet data=" + packet.input.goto(0).readAllBytes().toUHexString()) +} @Synchronized diff --git a/mirai-core/src/main/java/net/mamoe/mirai/utils/config/MiraiSynchronizedLinkedListMap.java b/mirai-core/src/main/java/net/mamoe/mirai/utils/MiraiSynchronizedLinkedHashMap.java similarity index 89% rename from mirai-core/src/main/java/net/mamoe/mirai/utils/config/MiraiSynchronizedLinkedListMap.java rename to mirai-core/src/main/java/net/mamoe/mirai/utils/MiraiSynchronizedLinkedHashMap.java index 05dacd407..2c9b0673a 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/utils/config/MiraiSynchronizedLinkedListMap.java +++ b/mirai-core/src/main/java/net/mamoe/mirai/utils/MiraiSynchronizedLinkedHashMap.java @@ -1,4 +1,4 @@ -package net.mamoe.mirai.utils.config; +package net.mamoe.mirai.utils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -16,15 +16,15 @@ import java.util.function.Function; * * @author NaturalHG */ -public class MiraiSynchronizedLinkedListMap extends AbstractMap { +public class MiraiSynchronizedLinkedHashMap extends AbstractMap { - public MiraiSynchronizedLinkedListMap(){ + public MiraiSynchronizedLinkedHashMap() { this.sortedMap = Collections.synchronizedMap(new LinkedHashMap<>()); } protected final Map sortedMap; - public MiraiSynchronizedLinkedListMap(LinkedHashMap map){ + public MiraiSynchronizedLinkedHashMap(LinkedHashMap map) { this.sortedMap = Collections.synchronizedMap(map); } @@ -155,13 +155,13 @@ public class MiraiSynchronizedLinkedListMap extends AbstractMap { return this.sortedMap.merge(key,value,remappingFunction); } - public boolean equals(MiraiSynchronizedLinkedListMap o) { + public boolean equals(MiraiSynchronizedLinkedHashMap o) { return this.sortedMap.equals(o.sortedMap); } @Override public boolean equals(Object o) { - return o instanceof MiraiSynchronizedLinkedListMap?this.equals((MiraiSynchronizedLinkedListMap)o):super.equals(o); + return o instanceof MiraiSynchronizedLinkedHashMap ? this.equals((MiraiSynchronizedLinkedHashMap) o) : super.equals(o); } } diff --git a/mirai-core/src/main/java/net/mamoe/mirai/utils/MiraiSynchronizedLinkedList.java b/mirai-core/src/main/java/net/mamoe/mirai/utils/MiraiSynchronizedLinkedList.java new file mode 100644 index 000000000..c88aefc87 --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/utils/MiraiSynchronizedLinkedList.java @@ -0,0 +1,195 @@ +package net.mamoe.mirai.utils; + +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.function.Consumer; +import java.util.function.IntFunction; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; + +/** + * @author Him188moe + */ +public class MiraiSynchronizedLinkedList extends AbstractList { + @SuppressWarnings("WeakerAccess") + protected final List syncList; + + public MiraiSynchronizedLinkedList() { + this.syncList = Collections.synchronizedList(new LinkedList<>()); + } + + public MiraiSynchronizedLinkedList(Collection collection) { + this.syncList = Collections.synchronizedList(new LinkedList<>(collection)); + } + + + @Override + public E get(int index) { + return this.syncList.get(index); + } + + @Override + public void forEach(Consumer action) { + this.syncList.forEach(action); + } + + @Override + public Spliterator spliterator() { + return this.syncList.spliterator(); + } + + @Override + public Stream stream() { + return this.syncList.stream(); + } + + @Override + public Stream parallelStream() { + return this.syncList.parallelStream(); + } + + @Override + public int size() { + return this.syncList.size(); + } + + @SuppressWarnings("SuspiciousToArrayCall") + @Override + public T[] toArray(IntFunction generator) { + return this.syncList.toArray(generator); + } + + @Override + public boolean removeIf(Predicate filter) { + return this.syncList.removeIf(filter); + } + + @Override + public void replaceAll(UnaryOperator operator) { + this.syncList.replaceAll(operator); + } + + @Override + public void sort(Comparator c) { + this.syncList.sort(c); + } + + @Override + public boolean add(E e) { + return this.syncList.add(e); + } + + @Override + public E set(int index, E element) { + return this.syncList.set(index, element); + } + + @Override + public void add(int index, E element) { + this.syncList.add(index, element); + } + + @Override + public E remove(int index) { + return this.syncList.remove(index); + } + + @Override + public int indexOf(Object o) { + return this.syncList.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return this.syncList.lastIndexOf(o); + } + + @Override + public void clear() { + this.syncList.clear(); + } + + @Override + public boolean addAll(int index, Collection c) { + return this.syncList.addAll(index, c); + } + + @NotNull + @Override + public Iterator iterator() { + return this.syncList.iterator(); + } + + @NotNull + @Override + public ListIterator listIterator() { + return this.syncList.listIterator(); + } + + @NotNull + @Override + public ListIterator listIterator(int index) { + return this.syncList.listIterator(index); + } + + @NotNull + @Override + public List subList(int fromIndex, int toIndex) { + return this.syncList.subList(fromIndex, toIndex); + } + + @Override + public int hashCode() { + return this.syncList.hashCode(); + } + + @Override + public boolean isEmpty() { + return this.syncList.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return this.syncList.contains(o); + } + + @NotNull + @Override + public Object[] toArray() { + return this.syncList.toArray(); + } + + @SuppressWarnings("SuspiciousToArrayCall") + @NotNull + @Override + public T[] toArray(@NotNull T[] a) { + return this.syncList.toArray(a); + } + + @Override + public boolean remove(Object o) { + return this.syncList.remove(o); + } + + @Override + public boolean containsAll(@NotNull Collection c) { + return this.syncList.containsAll(c); + } + + @Override + public boolean addAll(@NotNull Collection c) { + return this.syncList.addAll(c); + } + + @Override + public boolean removeAll(@NotNull Collection c) { + return this.syncList.removeAll(c); + } + + @Override + public boolean retainAll(@NotNull Collection c) { + return this.syncList.retainAll(c); + } +} diff --git a/mirai-core/src/main/java/net/mamoe/mirai/utils/ProtocolBuff.kt b/mirai-core/src/main/java/net/mamoe/mirai/utils/ProtocolBuff.kt deleted file mode 100644 index 16f23cc26..000000000 --- a/mirai-core/src/main/java/net/mamoe/mirai/utils/ProtocolBuff.kt +++ /dev/null @@ -1,48 +0,0 @@ -package net.mamoe.mirai.utils - -import java.io.DataOutputStream - -/** - * Google ProtocolBuff 的一些算法实现 - * - * @author Him188moe - */ - - -/** - * 128(10000000) -> 0x7F (10000000_10000001) - * - * TODO improve - */ -@ExperimentalUnsignedTypes -fun DataOutputStream.writeProtoFixedInt(int: Long) { - if (int == 0xFFL) { - this.writeShort(0x80_01)//unsigned//1000000010000001 - return - } - this.writeByte((int.rem(0xFF) + 0xFF).toInt()) - this.writeByte((int / 0xFF).toInt()) -} - -/** - * 127(1111111(7)) -> 0x7F (11111111(8)) - * - * TODO improve - */ -@ExperimentalUnsignedTypes -fun DataOutputStream.writeProtoInt(int: Long) { - if (int < 0xFF) { - this.writeByte((int and 0xFF).toInt())//10000000 - return - } - this.writeProtoFixedInt(int) -} - - -@ExperimentalUnsignedTypes -fun main() { - println() - println(lazyEncode { - it.writeProtoInt(128) - }.toUHexString()) -} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/utils/TEA.kt b/mirai-core/src/main/java/net/mamoe/mirai/utils/TEA.kt index d48de305a..852e96cb0 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/utils/TEA.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/utils/TEA.kt @@ -1,277 +1,269 @@ package net.mamoe.mirai.utils -import net.mamoe.mirai.network.Protocol import java.nio.ByteBuffer import java.util.* import kotlin.experimental.and import kotlin.experimental.xor -/** - * @author Him188moe - */ /** * TEA 加密 * * @author iweiz https://github.com/iweizime/StepChanger/blob/master/app/src/main/java/me/iweizi/stepchanger/qq/Cryptor.java */ -class TEA(private val key: ByteArray) { +object TEA { + private const val UINT32_MASK = 0xffffffffL + private fun doOption(data: ByteArray, key: ByteArray, encrypt: Boolean): ByteArray { + val mRandom = Random() + lateinit var mOutput: ByteArray + lateinit var mInBlock: ByteArray + var mIndexPos: Int + lateinit var mIV: ByteArray + var mOutPos = 0 + var mPreOutPos = 0 + var isFirstBlock = true - companion object { - val CRYPTOR_SHARE_KEY = TEA(Protocol.hexToBytes(Protocol.shareKey)) - val CRYPTOR_0825KEY = TEA(Protocol.hexToBytes(Protocol.key0825)) + val mKey = LongArray(4) - private val UINT32_MASK = 0xffffffffL + for (i in 0..3) { + mKey[i] = pack(key, i * 4, 4) + } - fun doOption(data: ByteArray, key: ByteArray, encrypt: Boolean): ByteArray { - val mRandom = Random() - lateinit var mOutput: ByteArray - lateinit var mInBlock: ByteArray - var mIndexPos: Int - lateinit var mIV: ByteArray - var mOutPos = 0 - var mPreOutPos = 0 - var isFirstBlock: Boolean = true + fun rand(): Int { + return mRandom.nextInt() + } - val mKey = LongArray(4) + fun encode(bytes: ByteArray): ByteArray { + var v0 = pack(bytes, 0, 4) + var v1 = pack(bytes, 4, 4) + var sum: Long = 0 + val delta = 0x9e3779b9L + for (i in 0..15) { + sum = sum + delta and UINT32_MASK + v0 += (v1 shl 4) + mKey[0] xor v1 + sum xor v1.ushr(5) + mKey[1] + v0 = v0 and UINT32_MASK + v1 += (v0 shl 4) + mKey[2] xor v0 + sum xor v0.ushr(5) + mKey[3] + v1 = v1 and UINT32_MASK + } + return ByteBuffer.allocate(8).putInt(v0.toInt()).putInt(v1.toInt()).array() + } - for (i in 0..3) { - mKey[i] = pack(key, i * 4, 4) + fun decode(bytes: ByteArray, offset: Int): ByteArray { + var v0 = pack(bytes, offset, 4) + var v1 = pack(bytes, offset + 4, 4) + val delta = 0x9e3779b9L + var sum = delta shl 4 and UINT32_MASK + for (i in 0..15) { + v1 -= (v0 shl 4) + mKey[2] xor v0 + sum xor v0.ushr(5) + mKey[3] + v1 = v1 and UINT32_MASK + v0 -= (v1 shl 4) + mKey[0] xor v1 + sum xor v1.ushr(5) + mKey[1] + v0 = v0 and UINT32_MASK + sum = sum - delta and UINT32_MASK + } + return ByteBuffer.allocate(8).putInt(v0.toInt()).putInt(v1.toInt()).array() + } + + fun encodeOneBlock() { + mIndexPos = 0 + while (mIndexPos < 8) { + mInBlock[mIndexPos] = if (isFirstBlock) + mInBlock[mIndexPos] + else + (mInBlock[mIndexPos] xor mOutput[mPreOutPos + mIndexPos]) + mIndexPos++ } - fun rand(): Int { - return mRandom.nextInt() + System.arraycopy(encode(mInBlock), 0, mOutput, mOutPos, 8) + mIndexPos = 0 + while (mIndexPos < 8) { + val outPos = mOutPos + mIndexPos + mOutput[outPos] = (mOutput[outPos] xor mIV[mIndexPos]) + mIndexPos++ } + System.arraycopy(mInBlock, 0, mIV, 0, 8) + mPreOutPos = mOutPos + mOutPos += 8 + mIndexPos = 0 + isFirstBlock = false + } - fun encode(bytes: ByteArray): ByteArray { - var v0 = pack(bytes, 0, 4) - var v1 = pack(bytes, 4, 4) - var sum: Long = 0 - val delta = 0x9e3779b9L - for (i in 0..15) { - sum = sum + delta and UINT32_MASK - v0 += (v1 shl 4) + mKey[0] xor v1 + sum xor v1.ushr(5) + mKey[1] - v0 = v0 and UINT32_MASK - v1 += (v0 shl 4) + mKey[2] xor v0 + sum xor v0.ushr(5) + mKey[3] - v1 = v1 and UINT32_MASK - } - return ByteBuffer.allocate(8).putInt(v0.toInt()).putInt(v1.toInt()).array() - } - - fun decode(bytes: ByteArray, offset: Int): ByteArray { - var v0 = pack(bytes, offset, 4) - var v1 = pack(bytes, offset + 4, 4) - val delta = 0x9e3779b9L - var sum = delta shl 4 and UINT32_MASK - for (i in 0..15) { - v1 -= (v0 shl 4) + mKey[2] xor v0 + sum xor v0.ushr(5) + mKey[3] - v1 = v1 and UINT32_MASK - v0 -= (v1 shl 4) + mKey[0] xor v1 + sum xor v1.ushr(5) + mKey[1] - v0 = v0 and UINT32_MASK - sum = sum - delta and UINT32_MASK - } - return ByteBuffer.allocate(8).putInt(v0.toInt()).putInt(v1.toInt()).array() - } - - fun encodeOneBlock() { - mIndexPos = 0 - while (mIndexPos < 8) { - mInBlock[mIndexPos] = if (isFirstBlock) - mInBlock[mIndexPos] - else - (mInBlock[mIndexPos] xor mOutput[mPreOutPos + mIndexPos]) + fun decodeOneBlock(ciphertext: ByteArray, offset: Int, len: Int): Boolean { + mIndexPos = 0 + while (mIndexPos < 8) { + if (mOutPos + mIndexPos < len) { + mIV[mIndexPos] = (mIV[mIndexPos] xor ciphertext[mOutPos + offset + mIndexPos]) mIndexPos++ + continue } - - System.arraycopy(encode(mInBlock), 0, mOutput, mOutPos, 8) - mIndexPos = 0 - while (mIndexPos < 8) { - val out_pos = mOutPos + mIndexPos - mOutput[out_pos] = (mOutput[out_pos] xor mIV[mIndexPos]) - mIndexPos++ - } - System.arraycopy(mInBlock, 0, mIV, 0, 8) - mPreOutPos = mOutPos - mOutPos += 8 - mIndexPos = 0 - isFirstBlock = false - } - - fun decodeOneBlock(ciphertext: ByteArray, offset: Int, len: Int): Boolean { - mIndexPos = 0 - while (mIndexPos < 8) { - if (mOutPos + mIndexPos < len) { - mIV[mIndexPos] = (mIV[mIndexPos] xor ciphertext[mOutPos + offset + mIndexPos]) - mIndexPos++ - continue - } - return true - } - - mIV = decode(mIV, 0) - mOutPos += 8 - mIndexPos = 0 return true - } - fun encrypt(plaintext: ByteArray, offset: Int, len: Int): ByteArray { - var len = len; - var offset = offset; - mInBlock = ByteArray(8) - mIV = ByteArray(8) - mOutPos = 0 - mPreOutPos = 0 - isFirstBlock = true - mIndexPos = (len + 10) % 8 - if (mIndexPos != 0) { - mIndexPos = 8 - mIndexPos - } - mOutput = ByteArray(mIndexPos + len + 10) - mInBlock[0] = (rand() and 0xf8 or mIndexPos).toByte() - for (i in 1..mIndexPos) { - mInBlock[i] = (rand() and 0xff).toByte() - } - ++mIndexPos - for (i in 0..7) { - mIV[i] = 0 - } + mIV = decode(mIV, 0) + mOutPos += 8 + mIndexPos = 0 + return true - var g = 0 - while (g < 2) { - if (mIndexPos < 8) { - mInBlock[mIndexPos++] = (rand() and 0xff).toByte() - ++g - } - if (mIndexPos == 8) { - encodeOneBlock() - } - } + } - while (len > 0) { - if (mIndexPos < 8) { - mInBlock[mIndexPos++] = plaintext[offset++] - } - if (mIndexPos == 8) { - encodeOneBlock() - } - len-- - } - g = 0 - while (g < 7) { - if (mIndexPos < 8) { - mInBlock[mIndexPos++] = 0.toByte() - } - if (mIndexPos == 8) { - encodeOneBlock() - } - g++ - } - return mOutput + @Suppress("NAME_SHADOWING") + fun encrypt(plaintext: ByteArray, offset: Int, len: Int): ByteArray { + var len = len + var offset = offset + mInBlock = ByteArray(8) + mIV = ByteArray(8) + mOutPos = 0 + mPreOutPos = 0 + isFirstBlock = true + mIndexPos = (len + 10) % 8 + if (mIndexPos != 0) { + mIndexPos = 8 - mIndexPos + } + mOutput = ByteArray(mIndexPos + len + 10) + mInBlock[0] = (rand() and 0xf8 or mIndexPos).toByte() + for (i in 1..mIndexPos) { + mInBlock[i] = (rand() and 0xff).toByte() + } + ++mIndexPos + for (i in 0..7) { + mIV[i] = 0 } - fun decrypt(cipherText: ByteArray, offset: Int, len: Int): ByteArray? { - require(!(len % 8 != 0 || len < 16)) { "must len % 8 == 0 && len >= 16" } - mIV = decode(cipherText, offset) - mIndexPos = (mIV[0] and 7).toInt() - var plen = len - mIndexPos - 10 - isFirstBlock = true - if (plen < 0) { - return null + var g = 0 + while (g < 2) { + if (mIndexPos < 8) { + mInBlock[mIndexPos++] = (rand() and 0xff).toByte() + ++g } - mOutput = ByteArray(plen) - mPreOutPos = 0 - mOutPos = 8 - ++mIndexPos - var g = 0 - while (g < 2) { - if (mIndexPos < 8) { - ++mIndexPos - ++g - } - if (mIndexPos == 8) { - isFirstBlock = false - if (!decodeOneBlock(cipherText, offset, len)) { - throw RuntimeException("Unable to decode") - } - } + if (mIndexPos == 8) { + encodeOneBlock() } + } - var outpos = 0 - while (plen != 0) { - if (mIndexPos < 8) { - mOutput[outpos++] = if (isFirstBlock) - mIV[mIndexPos] - else - (cipherText[mPreOutPos + offset + mIndexPos] xor mIV[mIndexPos]) + while (len > 0) { + if (mIndexPos < 8) { + mInBlock[mIndexPos++] = plaintext[offset++] + } + if (mIndexPos == 8) { + encodeOneBlock() + } + len-- + } + g = 0 + while (g < 7) { + if (mIndexPos < 8) { + mInBlock[mIndexPos++] = 0.toByte() + } + if (mIndexPos == 8) { + encodeOneBlock() + } + g++ + } + return mOutput + } + + fun decrypt(cipherText: ByteArray, offset: Int, len: Int): ByteArray? { + require(!(len % 8 != 0 || len < 16)) { "must len % 8 == 0 && len >= 16" } + mIV = decode(cipherText, offset) + mIndexPos = (mIV[0] and 7).toInt() + var plen = len - mIndexPos - 10 + isFirstBlock = true + if (plen < 0) { + return null + } + mOutput = ByteArray(plen) + mPreOutPos = 0 + mOutPos = 8 + ++mIndexPos + var g = 0 + while (g < 2) { + if (mIndexPos < 8) { + ++mIndexPos + ++g + } + if (mIndexPos == 8) { + isFirstBlock = false + if (!decodeOneBlock(cipherText, offset, len)) { + throw RuntimeException("Unable to decode") + } + } + } + + var outpos = 0 + while (plen != 0) { + if (mIndexPos < 8) { + mOutput[outpos++] = if (isFirstBlock) + mIV[mIndexPos] + else + (cipherText[mPreOutPos + offset + mIndexPos] xor mIV[mIndexPos]) + ++mIndexPos + } + if (mIndexPos == 8) { + mPreOutPos = mOutPos - 8 + isFirstBlock = false + if (!decodeOneBlock(cipherText, offset, len)) { + throw RuntimeException("Unable to decode") + } + } + plen-- + } + g = 0 + while (g < 7) { + if (mIndexPos < 8) { + if (cipherText[mPreOutPos + offset + mIndexPos].xor(mIV[mIndexPos]).toInt() != 0) { + throw RuntimeException() + } else { ++mIndexPos } - if (mIndexPos == 8) { - mPreOutPos = mOutPos - 8 - isFirstBlock = false - if (!decodeOneBlock(cipherText, offset, len)) { - throw RuntimeException("Unable to decode") - } - } - plen-- } - g = 0 - while (g < 7) { - if (mIndexPos < 8) { - if (cipherText[mPreOutPos + offset + mIndexPos].xor(mIV[mIndexPos]).toInt() != 0) { - throw RuntimeException() - } else { - ++mIndexPos - } + + if (mIndexPos == 8) { + mPreOutPos = mOutPos + if (!decodeOneBlock(cipherText, offset, len)) { + throw RuntimeException("Unable to decode") } - - if (mIndexPos == 8) { - mPreOutPos = mOutPos - if (!decodeOneBlock(cipherText, offset, len)) { - throw RuntimeException("Unable to decode") - } - } - g++ } - return mOutput + g++ } + return mOutput + } - return if (encrypt) { - encrypt(data, 0, data.size) - } else { - try { - return decrypt(data, 0, data.size)!! - } catch (e: Exception) { - println("Source: " + data.toUHexString(" ")) - println("Key: " + key.toUHexString(" ")) - throw e - } + return if (encrypt) { + encrypt(data, 0, data.size) + } else { + try { + return decrypt(data, 0, data.size)!! + } catch (e: Exception) { + println("Source: " + data.toUHexString(" ")) + println("Key: " + key.toUHexString(" ")) + throw e } } - - fun encrypt(source: ByteArray, key: ByteArray): ByteArray { - return doOption(source, key, true) - } - - fun encrypt(source: ByteArray, keyHex: String): ByteArray { - return encrypt(source, keyHex.hexToBytes()) - } - - fun decrypt(source: ByteArray, key: ByteArray): ByteArray { - return doOption(source, key, false) - } - - fun decrypt(source: ByteArray, keyHex: String): ByteArray { - return decrypt(source, keyHex.hexToBytes()) - } - - private fun pack(bytes: ByteArray, offset: Int, len: Int): Long { - var result: Long = 0 - val max_offset = if (len > 8) offset + 8 else offset + len - for (index in offset until max_offset) { - result = result shl 8 or (bytes[index].toLong() and 0xffL) - } - return result shr 32 or (result and UINT32_MASK) - } + } + + fun encrypt(source: ByteArray, key: ByteArray): ByteArray { + return doOption(source, key, true) + } + + @Suppress("unused") + fun encrypt(source: ByteArray, keyHex: String): ByteArray { + return encrypt(source, keyHex.hexToBytes()) + } + + fun decrypt(source: ByteArray, key: ByteArray): ByteArray { + return doOption(source, key, false) + } + + fun decrypt(source: ByteArray, keyHex: String): ByteArray { + return decrypt(source, keyHex.hexToBytes()) + } + + @Suppress("SameParameterValue") + private fun pack(bytes: ByteArray, offset: Int, len: Int): Long { + var result: Long = 0 + val maxOffset = if (len > 8) offset + 8 else offset + len + for (index in offset until maxOffset) { + result = result shl 8 or (bytes[index].toLong() and 0xffL) + } + return result shr 32 or (result and UINT32_MASK) } } \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/utils/Tested.kt b/mirai-core/src/main/java/net/mamoe/mirai/utils/Tested.kt index 5013309b3..d9bd874b7 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/utils/Tested.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/utils/Tested.kt @@ -5,4 +5,4 @@ package net.mamoe.mirai.utils * * @author Him188moe */ -internal annotation class TestedSuccessfully \ No newline at end of file +internal annotation class Tested \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/utils/Utils.kt b/mirai-core/src/main/java/net/mamoe/mirai/utils/Utils.kt index 776723608..788ec66a4 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/utils/Utils.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/utils/Utils.kt @@ -1,12 +1,16 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") + package net.mamoe.mirai.utils import net.mamoe.mirai.network.Protocol +import java.awt.image.BufferedImage import java.io.ByteArrayOutputStream import java.io.DataOutputStream import java.io.File import java.lang.reflect.Field import java.util.* import java.util.zip.CRC32 +import javax.imageio.ImageIO /** * @author Him188moe @@ -24,14 +28,14 @@ fun ByteArray.toHexString(separator: String = " "): String = this.joinToString(s return@joinToString ret } -@ExperimentalUnsignedTypes + fun ByteArray.toUHexString(separator: String = " "): String = this.toUByteArray().toUHexString(separator) -@ExperimentalUnsignedTypes + @JvmSynthetic fun ByteArray.toUHexString(): String = this.toUByteArray().toUHexString() -@ExperimentalUnsignedTypes + @JvmSynthetic fun UByteArray.toUHexString(separator: String = " "): String { return this.joinToString(separator) { @@ -43,36 +47,36 @@ fun UByteArray.toUHexString(separator: String = " "): String { } } -@ExperimentalUnsignedTypes + @JvmSynthetic fun UByteArray.toUHexString(): String = this.toUHexString(" ") -@ExperimentalUnsignedTypes + fun Byte.toUHexString(): String = this.toUByte().toString(16) -@ExperimentalUnsignedTypes + fun String.hexToBytes(): ByteArray = Protocol.hexToBytes(this) -@ExperimentalUnsignedTypes + fun String.hexToUBytes(): UByteArray = Protocol.hexToUBytes(this) -@ExperimentalUnsignedTypes + fun String.hexToInt(): Int = hexToBytes().toUInt().toInt() -@ExperimentalUnsignedTypes + fun ByteArray.toUInt(): UInt = this[0].toUInt().and(255u).shl(24) + this[1].toUInt().and(255u).shl(16) + this[2].toUInt().and(255u).shl(8) + this[3].toUInt().and(255u).shl(0) open class ByteArrayDataOutputStream : DataOutputStream(ByteArrayOutputStream()) { open fun toByteArray(): ByteArray = (out as ByteArrayOutputStream).toByteArray() - @ExperimentalUnsignedTypes + open fun toUByteArray(): UByteArray = (out as ByteArrayOutputStream).toByteArray().toUByteArray() } @JvmSynthetic -fun lazyEncode(t: (ByteArrayDataOutputStream) -> Unit): ByteArray = ByteArrayDataOutputStream().let { t(it); return it.toByteArray() } +fun lazyEncode(t: (ByteArrayDataOutputStream) -> Unit): ByteArray = ByteArrayDataOutputStream().also(t).toByteArray() + -@ExperimentalUnsignedTypes fun getRandomByteArray(length: Int): ByteArray { val bytes = LinkedList() repeat(length) { bytes.add((Math.random() * 255).toByte()) } @@ -134,4 +138,10 @@ fun ByteArray.removeZeroTail(): ByteArray { --i } return this.copyOfRange(0, i + 1) +} + +fun BufferedImage.toByteArray(formatName: String = "PNG"): ByteArray { + return lazyEncode { + ImageIO.write(this, formatName, it) + } } \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/utils/Varint.kt b/mirai-core/src/main/java/net/mamoe/mirai/utils/Varint.kt new file mode 100644 index 000000000..289749a18 --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/utils/Varint.kt @@ -0,0 +1,133 @@ +@file:JvmName("Varint") +@file:Suppress("EXPERIMENTAL_API_USAGE") + +package net.mamoe.mirai.utils + +import java.io.DataInputStream +import java.io.DataOutputStream +import java.io.IOException +import java.io.InputStream +import kotlin.experimental.or + +/** + * Tool class for VarInt or VarLong operations. + * + * Some code from http://wiki.vg/Protocol. + * + * @author MagicDroidX of Nukkit Project + * @author lmlstarqaq of Nukkit Project + */ + +fun encodeZigZag32(signedInt: Int): Long { + return (signedInt shl 1 xor (signedInt shr 31)).toLong() +} + + +@JvmSynthetic +fun decodeZigZag32(uint: UInt): Int { + return decodeZigZag32(uint.toLong()) +} + +fun decodeZigZag32(uint: Long): Int { + return (uint shr 1).toInt() xor -(uint and 1).toInt() +} + +fun encodeZigZag64(signedLong: Long): Long { + return signedLong shl 1 xor (signedLong shr 63) +} + +fun decodeZigZag64(signedLong: Long): Long { + return signedLong.ushr(1) xor -(signedLong and 1) +} + + +@Throws(IOException::class) +fun DataInputStream.readVarInt(): Int { + return decodeZigZag32(this.readUnsignedVarInt()) +} + + +@Throws(IOException::class) +fun DataInputStream.readUnsignedVarInt(): UInt { + return read(this, 5).toUInt() +} + + +@Throws(IOException::class) +fun DataInputStream.readVarLong(): Long { + return decodeZigZag64(readUnsignedVarLong().toLong()) +} + + +@Throws(IOException::class) +fun DataInputStream.readUnsignedVarLong(): ULong { + return read(this, 10).toULong() +} + +@Throws(IOException::class) +fun DataOutputStream.writeVarInt(signedInt: Int) { + this.writeUVarInt(encodeZigZag32(signedInt)) +} + + +@Throws(IOException::class) +fun DataOutputStream.writeUVarInt(uint: UInt) { + return writeUVarInt(uint.toLong()) +} + +@Throws(IOException::class) +fun DataOutputStream.writeUVarInt(uint: Long) { + this.write0(uint) +} + +@Throws(IOException::class) +fun DataOutputStream.writeVarLong(signedLong: Long) { + this.writeUVarLong(encodeZigZag64(signedLong)) +} + +@Throws(IOException::class) +fun DataOutputStream.writeUVarLong(ulong: Long) { + this.write0(ulong) +} + + +@Throws(IOException::class) +private fun DataOutputStream.write0(long: Long) { + var value = long + do { + var temp = (value and 127).toByte() + value = value ushr 7 + if (value != 0L) { + temp = temp or 128.toByte() + } + this.writeByte(temp.toInt()) + } while (value != 0L) +} + +@Throws(IOException::class) +private fun read(stream: DataInputStream, maxSize: Int): Long { + var value: Long = 0 + var size = 0 + var b = stream.readByte().toInt() + while (b and 0x80 == 0x80) { + value = value or ((b and 0x7F).toLong() shl size++ * 7) + require(size < maxSize) { "VarLong too big" } + b = stream.readByte().toInt() + } + + return value or ((b and 0x7F).toLong() shl size * 7) +} + +@Throws(IOException::class) +private fun read(stream: InputStream, maxSize: Int): Long { + var value: Long = 0 + var size = 0 + var b = stream.read() + while (b and 0x80 == 0x80) { + value = value or ((b and 0x7F).toLong() shl size++ * 7) + require(size < maxSize) { "VarLong too big" } + b = stream.read() + } + + return value or ((b and 0x7F).toLong() shl size * 7) +} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/utils/config/MiraiConfigSection.java b/mirai-core/src/main/java/net/mamoe/mirai/utils/config/MiraiConfigSection.java index 9c218d70b..a0a6cc6f2 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/utils/config/MiraiConfigSection.java +++ b/mirai-core/src/main/java/net/mamoe/mirai/utils/config/MiraiConfigSection.java @@ -1,5 +1,6 @@ package net.mamoe.mirai.utils.config; +import net.mamoe.mirai.utils.MiraiSynchronizedLinkedHashMap; import org.jetbrains.annotations.Nullable; import java.util.LinkedHashMap; @@ -10,7 +11,7 @@ import java.util.function.Supplier; /** * @author NaturalHG */ -public class MiraiConfigSection extends MiraiSynchronizedLinkedListMap { +public class MiraiConfigSection extends MiraiSynchronizedLinkedHashMap { public MiraiConfigSection(){ super(); diff --git a/mirai-core/src/test/java/PacketTest.kt b/mirai-core/src/test/java/PacketTest.kt deleted file mode 100644 index 4d7b5ffb9..000000000 --- a/mirai-core/src/test/java/PacketTest.kt +++ /dev/null @@ -1,47 +0,0 @@ -import net.mamoe.mirai.network.packet.login.ClientPasswordSubmissionPacket -import net.mamoe.mirai.utils.toUHexString - -@ExperimentalUnsignedTypes -fun main(){ - /* - val data = "00 37 13 08 25 31 01 EB 10 08 30 69 50 1C 84 A9 C2 16 D7 52 B9 1C 79 CA 5A CF FD BC EB 10 08 30 69 50 1C 84 A9 C2 16 D7 52 B9 1C 79 CA 5A CF FD BC AE D8 A6 BB DC 21 6E 79 26 E1 A2 23 11 AA B0 9A AE D8 A6 BB DC 21 6E 79 26 E1 A2 23 11 AA B0 9A 76 E4 B8 DD 03 00 00 00 01 2E 01 00 00 68 52 00 00 00 00 A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D C3 47 F0 25 A1 8E 74 EF 1E 0B 32 5B 20 8A FA 3B 0B 52 8F 86 E6 04 F1 D6 F8 63 75 60 8C 0C 7D 06 D1 E0 22 F8 49 EF AF 61 EE 7E 69 72 EB 10 08 30 69 50 1C 84 A9 C2 16 D7 52 B9 1C 79 CA 5A CF FD BC AE D8 A6 BB DC 21 6E 79 26 E1 A2 23 11 AA B0 9A 49 39 72 ED 61 12 B6 88 4D A2 56 23 E9 92 11 92 27 4A 70 00 C9 01 7B 03"; - val s = DataInputStream(data.hexToBytes().inputStream()) - val packet = ServerTouchResponsePacket(ServerTouchResponsePacket.Type.TYPE_08_25_31_01, s) - packet.decode() - System.out.println(packet.token.toUByteArray().toUHexString(" ")) - System.out.println(packet.loginTime.toUHexString(" ")) - System.out.println(packet.loginIP) - */ - - // val packet = ClientPasswordSubmissionPacket(1994701021,"xiaoqqq",) - - /* - val data = "00 01 09 00 70 00 01 5C 71 80 A6 BA 20 62 2E C1 BE BF F2 47 37 40 A1 00 38 91 25 85 58 18 D3 67 77 2C 4D 02 D8 66 A6 F7 3E 57 D8 CE 01 47 7F D0 8F 13 C8 3A E5 19 A2 60 BC 4C 9A 35 4E 92 9F 21 48 6C 67 68 36 6B 94 C1 6F 11 8D 55 6B 04 9A 22 C3 00 20 29 7E D4 A7 16 02 07 14 41 90 3A 65 06 AC CB 28 AB 90 DB 46 33 C9 C0 1D 06 44 7A 92 17 C3 A5 F3 00 00 01 03 00 14 00 01 00 10 60 C9 5D A7 45 70 04 7F 21 7D 84 50 5C 66 A5 C6 01 07 01 D3 00 01 00 16 00 00 00 01 00 00 00 64 00 00 0D B6 00 09 3A 80 00 00 00 3C 00 02 2E 29 4E 47 5F 68 78 2C 47 25 5F 59 50 65 60 5D 00 88 00 04 5D 59 45 17 B7 5F F8 D4 00 00 00 00 00 78 38 E0 3B 23 4A C5 0E 93 CB C1 66 96 37 8B 46 B2 86 23 3F 2D 09 45 E0 16 1C E9 9C 11 7A FA 2D A8 50 47 42 74 01 06 84 76 0E 5F C6 04 29 1A 4A 65 AA 93 49 DF BD 00 ED 80 B8 26 CA 80 E8 20 6D 15 43 DD D8 E6 48 C2 8A 5A F8 70 6B 51 3A E2 2D 21 95 4B 6A 75 A8 90 CA B1 C0 E5 73 99 D7 59 D8 DD 3D C9 5C E4 49 61 22 11 60 85 48 C4 7D E0 84 62 AD B3 13 84 61 C1 9E 19 35 41 44 44 37 3F 21 33 64 4B 37 5D 77 6D 61 3F 00 78 00 01 5D 59 45 17 00 70 A4 D9 44 9E 95 51 B4 B0 91 CC 1E DB 34 F9 F7 13 8B 30 08 C0 AE 33 22 9C FF 87 CF 9B A2 B0 E5 E1 D0 E0 AD DD 8F E9 F6 1E 01 1F AA 74 46 66 B4 81 54 B9 29 E5 FC 0B 7F C9 13 AE 32 BA D6 55 2E B0 A1 30 24 B6 F2 E7 62 F9 2E 00 E4 51 61 50 7C D1 36 E8 61 96 36 FF B7 32 74 3C 2A F7 74 63 DA 7D 57 84 18 ED 84 E9 D8 87 6D 66 1D D5 84 D4 23 99 00 83 01 63 2A 69 2E 25 79 28 3B 29 33 29 40 28 54 7E 21 00 70 00 01 5D 59 45 17 00 68 6F 1F FB 31 7B D7 B7 D8 91 32 D7 20 8B 8A F6 02 C8 22 E5 24 8C 25 F2 6A C5 B0 ED 35 01 BF AF 42 72 33 4E FB 3F D3 02 BA F4 46 2B 68 20 0B E3 39 81 B1 D3 8A E0 1B 0F 69 D1 70 AE 49 A5 24 4F BB 58 4F F8 31 A0 37 4C CD F1 12 35 80 99 7D 25 CA F9 E9 45 B6 B0 57 56 66 61 C5 7B 90 57 BF E2 2C 94 91 80 1A B0 D7 21 A8 44 2C 33 4A 29 77 5F 71 40 41 38 3D 7A 41 65 33 01 08 00 29 00 01 00 25 00 1D 02 5B 14 28 E0 B9 91 E2 80 A2 CC 80 CF 89 E2 80 A2 CC 81 E0 B9 91 29 02 13 80 02 00 05 00 04 00 00 00 01 01 15 00 10 F9 86 85 81 30 F6 1B E0 E7 97 98 F6 46 C3 4F B2" - val s = DataInputStream(data.hexToBytes().inputStream()) - val packet = ServerLoginResponseSuccessPacket(s,(data.length+1)/3) - packet.decode() - System.out.println("0828key: \n" + packet._0828_rec_decr_key.toUByteArray().toUHexString(" ")) - System.out.println("token88: \n" + packet.token88.toUByteArray().toUHexString(" ")) - System.out.println("token38: \n" + packet.token38.toUByteArray().toUHexString(" ")) - System.out.println("enckey: \n" + packet.encryptionKey.toUByteArray().toUHexString(" ")) - System.out.println("nick: " + packet.nick) - System.out.println("age: " + packet.age) - System.out.println("gender: " + packet.gender) - */ - - /* - val data = "FB 01 04 03 33 00 01 00 BA 02 03 2C 13 00 05 01 00 00 01 23 00 38 F5 C3 CF F4 B4 27 C5 8F 9B D3 ED 18 73 7D E9 CB 43 1F 57 43 BE D3 1B 9A F5 26 2B F4 D9 43 14 9A ED 3B C3 6C E5 7F 4E B0 0C BA 55 57 18 06 78 E1 13 A7 B2 A8 7F 47 E1 1C 02 BC 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 00 00 00 82 00 00 00 35 08 03 00 00 00 BA 12 C3 02 00 00 00 04 67 41 4D 41 00 00 B1 8F 0B FC 61 05 00 00 00 01 73 52 47 42 00 AE CE 1C E9 00 00 00 6C 50 4C 54 45 F6 F8 F4 E3 EE FD E5 FD FF EE FD F0 F6 F9 E7 F7 F9 EC F4 FE FF F7 F7 F9 F5 F4 FF EC F7 FF E6 F4 E0 F5 FC F8 49 5B 8F CB DB FA BB C9 F5 2B 40 7C D6 E6 FF FD FB FF ED F1 F0 2A 43 B3 D6 F6 FF FC F1 FC 1C 38 91 A7 BD F6 63 74 BD 70 8B CC 34 4F A5 DE F3 F1 75 88 E7 A4 B7 D3 73 82 A1 8C A3 DC F3 FB D8 4D 68 B9 94 A8 FB 8D 9C B6 CB 42 B0 8E 00 00 0A 6F 49 44 41 54 58 C3 B4 57 89 76 EB 2A 12 14 20 B1 09 10 42 68 DF 2C FB FF FF 71 0A 79 49 E2 E4 CE CC 79 37 8F 13 59 B6 72 EC 2E AA BB AB 9A 2C FB 61 89 8F 95 E5 79 A9 70 CB B2 22 C7 2A 65 DB 5E 72 21 F1 A9 90 B2 28 C4 F9 AF 5F 5F 05 30 9C 28 D2 4B C6 58 F9 8C 92 17 39 13 6D 9B FF EB 10 8A 3B 0F 2F 10 52 64 45 51 64 42 A6 37 79 21 DA FC F2 6F 40 08 9F AE B7 9C A4 C8 27 31 F2 C4 50 A4 D4 9C F9 01 BC A2 90 0F 08 E1 2F AE 1F E3 7E 7D 24 52 F4 67 2C 91 02 E3 CA 13 2F 78 97 00 86 DF 4E C4 23 56 F1 F8 7B 10 90 42 3D D2 93 EE 27 04 F1 80 F0 4B 99 10 4F 62 5E D9 2D EE 57 8A F1 A0 E1 64 20 2B 55 F9 C8 C3 67 08 F8 81 7F F2 F7 BE FD 67 1D 9E 9B 4F EB 89 25 21 28 19 63 09 5D A9 94 2A 59 AA 87 B3 32 4E 84 FF 3C 13 8F AF 06 F1 29 FD E2 C1 02 54 E0 8C 5E 5C 2E A5 72 43 65 F4 E5 72 61 5C 4A 45 81 41 97 79 0A 9E FF 62 22 C2 EB 7E 42 48 45 80 2B 0F 58 B9 66 D6 1C 63 75 D3 97 BC F4 08 0F 08 5C 6B 8D CC 14 1F 10 C4 5F B4 C3 37 66 EE E5 78 CF 42 12 05 CD F5 4D 29 EB 36 AD 19 82 CF 76 35 C6 86 B2 94 A7 36 FC 62 39 BE A8 B8 D7 02 78 08 8F 66 E4 44 45 DF BB AB A3 34 92 14 DF 54 C6 32 1D 00 21 7F 41 08 7F C9 7F F8 28 88 4F B5 90 89 3A 21 90 9E DF E8 DE 34 4D 8C D1 EF C6 AC 6B 7F 74 8E B1 FA 84 90 7F B0 F0 CF 5A E2 27 4C AF 8E 28 EF 24 48 B2 AF B1 9A 96 3E F6 43 75 38 6F 29 5D 1A C7 59 78 87 F0 1B B2 F0 95 05 3C 93 B2 26 B6 9F BA 18 AB AE 59 EC 88 17 17 A9 8F 4B B3 CE 60 A1 CD F2 CB 03 42 10 7F D9 06 1F 76 80 8E 4F 6D 9F 56 99 05 25 A5 DD AA EB 62 7A 6F 87 E6 B8 0E 5D 33 C6 99 C6 C4 02 24 EB 74 8C 87 98 64 F8 5E A9 9E 1E F7 72 FB FF B3 21 8A A4 00 01 00 28 F9 59 C5 E6 34 43 53 95 C8 17 2E 62 78 BF E8 27 BF 20 BA 11 5A 74 D1 7C D0 95 6C F6 A3 41 D2 84 BD 7D F6 64 BC 27 40 50 01 15 00 10 44 98 EB B8 30 3B DE 7D 2B CC 4C 41 B3 1C 92 86" - val s = DataInputStream(data.hexToBytes().inputStream()) - val packet = ServerLoginResponseVerificationCodePacket(s,(data.length+1)/3) - packet.decode() - println(packet.token00BA.toUByteArray().toUHexString(" ")) - println(packet.verifyCode.toUByteArray().toUHexString(" ")) - println(packet.verifyCodeLength) - - File(System.getProperty("user.dir") + "/5.png").createNewFile() - packet.verifyCode.inputStream().transferTo(FileOutputStream(System.getProperty("user.dir") + "/5.png")) - */ - - val packet = ClientPasswordSubmissionPacket(1994701021, "xiaoqqq", 131513, "123.123.123.123", "tgtgtKey".toByteArray(), "".toByteArray()) - packet.encodeToByteArray().toUByteArray().toUHexString(" ") -} \ No newline at end of file diff --git a/pom.xml b/pom.xml index ed7631a11..1e2441ae9 100644 --- a/pom.xml +++ b/pom.xml @@ -17,10 +17,12 @@ + huawei https://mirrors.huaweicloud.com/repository/maven/ @@ -52,17 +54,24 @@ 1.18.8 compile - - - org.ini4j - ini4j - 0.5.2 - - + + + + com.google.protobuf + protobuf-java + 3.5.0 + + + + org.ini4j + ini4j + 0.5.2 + + net.java.dev.jna @@ -90,12 +99,6 @@ 4.1.38.Final - - org.ini4j - ini4j - 0.5.2 - - org.jetbrains.kotlin kotlin-stdlib @@ -123,12 +126,11 @@ - commons-httpclient - commons-httpclient - 3.1 + org.jsoup + jsoup + 1.12.1 compile -