mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-23 02:10:16 +08:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
86d64f026d
15
mirai-api/src/main/java/net/mamoe/mirai/Bot.java
Normal file
15
mirai-api/src/main/java/net/mamoe/mirai/Bot.java
Normal file
@ -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
|
||||
}
|
@ -17,6 +17,31 @@
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.pcap4j</groupId>
|
||||
<artifactId>pcap4j-core</artifactId>
|
||||
<version>1.8.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.pcap4j</groupId>
|
||||
<artifactId>pcap4j-packetfactory-static</artifactId>
|
||||
<version>1.8.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.mamoe</groupId>
|
||||
<artifactId>mirai-api</artifactId>
|
||||
<version>1.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlinx</groupId>
|
||||
<artifactId>kotlinx-coroutines-core</artifactId>
|
||||
@ -56,7 +81,12 @@
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.12.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ini4j</groupId>
|
||||
<artifactId>ini4j</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
|
@ -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;
|
||||
* <br>
|
||||
* {@link Bot} 由 3 个模块组成.
|
||||
* {@linkplain ContactSystem 联系人管理}: 可通过 {@link Bot#contacts} 访问
|
||||
* {@linkplain BotNetworkHandler 网络处理器}: 可通过 {@link Bot#network} 访问
|
||||
* {@linkplain BotNetworkHandlerImpl 网络处理器}: 可通过 {@link Bot#network} 访问
|
||||
* {@linkplain BotAccount 机器人账号信息}: 可通过 {@link Bot#account} 访问
|
||||
* <br>
|
||||
* 若你需要得到机器人的 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)
|
||||
* <br>
|
||||
* 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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<Unit> {
|
||||
return image.upload(session, this)
|
||||
}
|
||||
|
||||
fun sendMessage(message: Message) {
|
||||
if (message is MessageChain) {
|
||||
return sendMessage(message)
|
||||
|
@ -26,7 +26,7 @@ class Group(bot: Bot, number: Long) : Contact(bot, number), Closeable {
|
||||
val members = ContactList<QQ>()
|
||||
|
||||
override fun sendMessage(message: MessageChain) {
|
||||
bot.network.messageHandler.sendGroupMessage(this, message)
|
||||
bot.network.message.sendGroupMessage(this, message)
|
||||
}
|
||||
|
||||
override fun sendXMLMessage(message: String) {
|
||||
|
@ -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) {
|
||||
|
@ -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),
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
@ -11,7 +11,7 @@ import java.util.stream.Stream
|
||||
class MessageChain : Message {
|
||||
override val type: Int = MessageId.CHAIN
|
||||
|
||||
internal val list = LinkedList<Message>()
|
||||
val list: MutableList<Message> = Collections.synchronizedList(LinkedList<Message>())
|
||||
|
||||
constructor(head: Message, tail: Message) {
|
||||
Objects.requireNonNull(head)
|
||||
@ -26,17 +26,16 @@ class MessageChain : Message {
|
||||
list.add(message)
|
||||
}
|
||||
|
||||
constructor()
|
||||
|
||||
fun toList(): List<Message> {
|
||||
return list.toList()
|
||||
constructor(messages: Collection<Message>) {
|
||||
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<Message> {
|
||||
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
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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<Unit> {
|
||||
return session.expectPacket<ServerTryUploadGroupImageResponsePacket> {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
@ -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<LoginState>
|
||||
|
||||
/**
|
||||
* 添加一个临时包处理器
|
||||
*
|
||||
* @see [TemporaryPacketHandler]
|
||||
*/
|
||||
fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>)
|
||||
|
||||
override fun close()
|
||||
}
|
@ -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<TemporaryPacketHandler<*>>())
|
||||
|
||||
override fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>) {
|
||||
temporaryPacketHandler.send(action.session)
|
||||
temporaryPacketHandlers.add(temporaryPacketHandler)
|
||||
}
|
||||
|
||||
//private | internal
|
||||
/**
|
||||
* 尝试登录
|
||||
*
|
||||
* @param touchingTimeoutMillis 连接每个服务器的 timeout
|
||||
*/
|
||||
override fun tryLogin(touchingTimeoutMillis: Long): CompletableFuture<LoginState> {
|
||||
val ipQueue: LinkedList<String> = LinkedList(Protocol.SERVER_IP)
|
||||
val future = CompletableFuture<LoginState>()
|
||||
|
||||
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<LoginState>? = 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<LoginState> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
@ -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<ServerPacketXXX> {
|
||||
* toSend { ClientPacketXXX(...) }
|
||||
* expect {//it: ServerPacketXXX
|
||||
*
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param P 期待的包
|
||||
* @param handlerTemporary 处理器.
|
||||
* @return future. 可进行超时处理
|
||||
*
|
||||
* Kotlin DSL: 仅 Kotlin 使用.
|
||||
*/
|
||||
@JvmSynthetic
|
||||
inline fun <reified P : ServerPacket> expectPacket(handlerTemporary: TemporaryPacketHandler<P>.() -> Unit): CompletableFuture<Unit> {
|
||||
val future = CompletableFuture<Unit>()
|
||||
this.bot.network.addHandler(TemporaryPacketHandler(P::class, future, this).also(handlerTemporary))
|
||||
return future
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送一个数据包, 并期待接受一个特定的 [ServerPacket].
|
||||
* 发送成功后, 该方法会等待收到 [ServerPacket] 直到超时.
|
||||
* 由于包名可能过长, 可使用 `DataPacketSocket.expectPacket(PacketProcessor)` 替代.
|
||||
*
|
||||
* 实现方法:
|
||||
* ```kotlin
|
||||
* session.expectPacket<ServerPacketXXX>(ClientPacketXXX(...)) {//it: ServerPacketXXX
|
||||
*
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param P 期待的包
|
||||
* @param toSend 将要发送的包
|
||||
* @param handler 处理期待的包
|
||||
* @return future. 可进行超时处理
|
||||
*
|
||||
* Kotlin DSL: 仅 Kotlin 使用.
|
||||
*/
|
||||
@JvmSynthetic
|
||||
inline fun <reified P : ServerPacket> expectPacket(toSend: ClientPacket, noinline handler: (P) -> Unit): CompletableFuture<Unit> {
|
||||
val future = CompletableFuture<Unit>()
|
||||
this.bot.network.addHandler(TemporaryPacketHandler(P::class, future, this).also {
|
||||
it.toSend { toSend }
|
||||
it.expect(handler)
|
||||
})
|
||||
return future
|
||||
}
|
||||
}
|
@ -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<Int, ByteArray> = 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 <= ' ' } }
|
||||
|
@ -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<KClass<out PacketHandler>, 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<LoginState> {
|
||||
val ipQueue: LinkedList<String> = LinkedList(Protocol.SERVER_IP)
|
||||
val future = CompletableFuture<LoginState>()
|
||||
|
||||
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<LoginState>? = 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<LoginState> {
|
||||
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 <P : ServerPacket> waitForPacket(packetClass: KClass<P>, 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<AddFriendSession>())
|
||||
|
||||
override fun onPacketReceived(packet: ServerPacket) {
|
||||
when (packet) {
|
||||
is ServerCanAddFriendResponsePacket -> {
|
||||
this.addFriendSessions.forEach {
|
||||
it.onPacketReceived(packet)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addFriend(qqNumber: Long, message: Supplier<String>) {
|
||||
addFriend(qqNumber, lazy { message.get() })
|
||||
}
|
||||
|
||||
@JvmSynthetic
|
||||
fun addFriend(qqNumber: Long, message: Lazy<String> = lazyOf("")): CompletableFuture<AddFriendResult> {
|
||||
val future = CompletableFuture<AddFriendResult>()
|
||||
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<AddFriendResult>,
|
||||
private val message: Lazy<String>
|
||||
) : 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<AddFriendSession>())
|
||||
private val uploadImageSessions = Collections.synchronizedCollection(mutableListOf<UploadImageSession>())
|
||||
|
||||
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<String>) {
|
||||
addFriend(qqNumber, lazy { message.get() })
|
||||
}
|
||||
|
||||
|
||||
@JvmSynthetic
|
||||
fun addFriend(qqNumber: Long, message: Lazy<String> = lazyOf("")): CompletableFuture<AddFriendResult> {
|
||||
val future = CompletableFuture<AddFriendResult>()
|
||||
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<AddFriendResult>,
|
||||
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<AddFriendResult>,
|
||||
private val message: Lazy<String>
|
||||
) : 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
@ -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<T : PacketHandler>(
|
||||
val clazz: Class<T>,
|
||||
val instance: T
|
||||
)
|
||||
|
||||
fun PacketHandler.asNode(): PacketHandlerNode<PacketHandler> {
|
||||
return PacketHandlerNode(this.javaClass, this)
|
||||
}
|
||||
|
||||
class PacketHandlerList : MiraiSynchronizedLinkedList<PacketHandlerNode<*>>() {
|
||||
|
||||
fun <T : PacketHandler> get(clazz: Class<T>): T {
|
||||
this.forEach {
|
||||
if (it.clazz == clazz) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return@get it.instance as T
|
||||
}
|
||||
}
|
||||
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
}
|
@ -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<P : ServerPacket>(
|
||||
private val expectationClass: KClass<P>,
|
||||
private val future: CompletableFuture<Unit>,
|
||||
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
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
@ -9,7 +9,7 @@ import java.io.DataInputStream
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
|
||||
@PacketId("00 5C")
|
||||
class ClientAccountInfoRequestPacket(
|
||||
private val qq: Long,
|
||||
|
@ -7,7 +7,7 @@ import java.io.IOException
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
|
||||
@PacketId("00 58")
|
||||
class ClientHeartbeatPacket(
|
||||
private val qq: Long,
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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 <N : Number> DataInputStream.goto(position: N): DataInputStream {
|
||||
this.reset()
|
||||
this.skip(position.toLong());
|
||||
this.skip(position.toLong())
|
||||
return this
|
||||
}
|
||||
|
||||
@ -221,7 +231,7 @@ fun <N : Number> 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 <N : Number> DataInputStream.readIntAt(position: N): Int {
|
||||
this.goto(position)
|
||||
return this.readInt();
|
||||
return this.readInt()
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
|
||||
fun <N : Number> DataInputStream.readUIntAt(position: N): UInt {
|
||||
this.goto(position)
|
||||
return this.readNBytes(4).toUInt();
|
||||
return this.readNBytes(4).toUInt()
|
||||
}
|
||||
|
||||
fun <N : Number> DataInputStream.readByteAt(position: N): Byte {
|
||||
this.goto(position)
|
||||
return this.readByte();
|
||||
return this.readByte()
|
||||
}
|
||||
|
||||
fun <N : Number> 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 <P : ServerPacket> Bot.waitForPacket(packetClass: KClass<P>, 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)
|
@ -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 -> {
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
|
@ -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) {
|
||||
|
@ -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())
|
||||
|
||||
/*
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -34,11 +34,6 @@ enum class LoginState {
|
||||
*/
|
||||
TAKEN_BACK,
|
||||
|
||||
/**
|
||||
* 需要验证码登录
|
||||
*/
|
||||
VERIFICATION_CODE,
|
||||
|
||||
/**
|
||||
* 未知. 更换服务器或等几分钟再登录可能解决.
|
||||
*/
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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)
|
@ -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<C extends Contact> extends MiraiSynchronizedLinkedListMap<Long, C> {
|
||||
public class ContactList<C extends Contact> extends MiraiSynchronizedLinkedHashMap<Long, C> {
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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<K,V> extends AbstractMap<K,V> {
|
||||
public class MiraiSynchronizedLinkedHashMap<K, V> extends AbstractMap<K, V> {
|
||||
|
||||
public MiraiSynchronizedLinkedListMap(){
|
||||
public MiraiSynchronizedLinkedHashMap() {
|
||||
this.sortedMap = Collections.synchronizedMap(new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
protected final Map<K, V> sortedMap;
|
||||
|
||||
public MiraiSynchronizedLinkedListMap(LinkedHashMap<K,V> map){
|
||||
public MiraiSynchronizedLinkedHashMap(LinkedHashMap<K, V> map) {
|
||||
this.sortedMap = Collections.synchronizedMap(map);
|
||||
}
|
||||
|
||||
@ -155,13 +155,13 @@ public class MiraiSynchronizedLinkedListMap<K,V> extends AbstractMap<K,V> {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -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<E> extends AbstractList<E> {
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
protected final List<E> syncList;
|
||||
|
||||
public MiraiSynchronizedLinkedList() {
|
||||
this.syncList = Collections.synchronizedList(new LinkedList<>());
|
||||
}
|
||||
|
||||
public MiraiSynchronizedLinkedList(Collection<E> collection) {
|
||||
this.syncList = Collections.synchronizedList(new LinkedList<>(collection));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public E get(int index) {
|
||||
return this.syncList.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<? super E> action) {
|
||||
this.syncList.forEach(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<E> spliterator() {
|
||||
return this.syncList.spliterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<E> stream() {
|
||||
return this.syncList.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<E> parallelStream() {
|
||||
return this.syncList.parallelStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.syncList.size();
|
||||
}
|
||||
|
||||
@SuppressWarnings("SuspiciousToArrayCall")
|
||||
@Override
|
||||
public <T> T[] toArray(IntFunction<T[]> generator) {
|
||||
return this.syncList.toArray(generator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeIf(Predicate<? super E> filter) {
|
||||
return this.syncList.removeIf(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceAll(UnaryOperator<E> operator) {
|
||||
this.syncList.replaceAll(operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sort(Comparator<? super E> 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<? extends E> c) {
|
||||
return this.syncList.addAll(index, c);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Iterator<E> iterator() {
|
||||
return this.syncList.iterator();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ListIterator<E> listIterator() {
|
||||
return this.syncList.listIterator();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ListIterator<E> listIterator(int index) {
|
||||
return this.syncList.listIterator(index);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<E> 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> 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<? extends E> 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);
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -5,4 +5,4 @@ package net.mamoe.mirai.utils
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
internal annotation class TestedSuccessfully
|
||||
internal annotation class Tested
|
@ -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<Byte>()
|
||||
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)
|
||||
}
|
||||
}
|
133
mirai-core/src/main/java/net/mamoe/mirai/utils/Varint.kt
Normal file
133
mirai-core/src/main/java/net/mamoe/mirai/utils/Varint.kt
Normal file
@ -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)
|
||||
}
|
@ -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<T> extends MiraiSynchronizedLinkedListMap<String, T> {
|
||||
public class MiraiConfigSection<T> extends MiraiSynchronizedLinkedHashMap<String, T> {
|
||||
|
||||
public MiraiConfigSection(){
|
||||
super();
|
||||
|
@ -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(" ")
|
||||
}
|
36
pom.xml
36
pom.xml
@ -17,10 +17,12 @@
|
||||
</modules>
|
||||
|
||||
<repositories>
|
||||
<!--
|
||||
<repository>
|
||||
<id>mamoe-repo</id>
|
||||
<url>http://mamoe.net:8081/repository/public/</url>
|
||||
</repository>
|
||||
-->
|
||||
<repository>
|
||||
<id>huawei</id>
|
||||
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
|
||||
@ -52,17 +54,24 @@
|
||||
<version>1.18.8</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ini4j</groupId>
|
||||
<artifactId>ini4j</artifactId>
|
||||
<version>0.5.2</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java</artifactId>
|
||||
<version>3.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ini4j</groupId>
|
||||
<artifactId>ini4j</artifactId>
|
||||
<version>0.5.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/net.java.dev.jna/jna -->
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
@ -90,12 +99,6 @@
|
||||
<version>4.1.38.Final</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ini4j</groupId>
|
||||
<artifactId>ini4j</artifactId>
|
||||
<version>0.5.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib</artifactId>
|
||||
@ -123,12 +126,11 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-httpclient</groupId>
|
||||
<artifactId>commons-httpclient</artifactId>
|
||||
<version>3.1</version>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.12.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user