mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-22 18:00:17 +08:00
send image
This commit is contained in:
parent
37fa8055b5
commit
ff0acc87e3
@ -17,6 +17,19 @@
|
||||
</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>
|
||||
|
@ -3,6 +3,7 @@ package net.mamoe.mirai;
|
||||
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;
|
||||
@ -53,7 +54,7 @@ public final class Bot implements Closeable {
|
||||
|
||||
public final ContactSystem contacts = new ContactSystem();
|
||||
|
||||
public final BotNetworkHandlerImpl network;
|
||||
public final BotNetworkHandler network;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
@ -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)
|
||||
|
@ -12,7 +12,7 @@ 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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
@ -2,15 +2,14 @@ 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.ActionPacketHandler
|
||||
import net.mamoe.mirai.network.handler.DataPacketSocket
|
||||
import net.mamoe.mirai.network.handler.MessagePacketHandler
|
||||
import net.mamoe.mirai.network.handler.PacketHandler
|
||||
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])的处理任务.
|
||||
@ -51,4 +50,15 @@ interface BotNetworkHandler : Closeable {
|
||||
* java 调用方式: `botNetWorkHandler.getAction()`
|
||||
*/
|
||||
var action: ActionPacketHandler
|
||||
|
||||
fun tryLogin(touchingTimeoutMillis: Long = 200): CompletableFuture<LoginState>
|
||||
|
||||
/**
|
||||
* 添加一个临时包处理器
|
||||
*
|
||||
* @see [TemporaryPacketHandler]
|
||||
*/
|
||||
fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>)
|
||||
|
||||
override fun close()
|
||||
}
|
@ -6,7 +6,6 @@ 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.hookWhile
|
||||
import net.mamoe.mirai.network.handler.*
|
||||
import net.mamoe.mirai.network.packet.*
|
||||
import net.mamoe.mirai.network.packet.login.*
|
||||
@ -21,7 +20,6 @@ import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ScheduledFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.imageio.ImageIO
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* [BotNetworkHandler] 的内部实现, 该类不会有帮助理解的注解, 请查看 [BotNetworkHandler] 以获取帮助
|
||||
@ -40,15 +38,20 @@ internal class BotNetworkHandlerImpl(private val bot: Bot) : BotNetworkHandler {
|
||||
|
||||
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
|
||||
*/
|
||||
@JvmOverloads
|
||||
internal fun tryLogin(touchingTimeoutMillis: Long = 200): CompletableFuture<LoginState> {
|
||||
override fun tryLogin(touchingTimeoutMillis: Long): CompletableFuture<LoginState> {
|
||||
val ipQueue: LinkedList<String> = LinkedList(Protocol.SERVER_IP)
|
||||
val future = CompletableFuture<LoginState>()
|
||||
|
||||
@ -101,6 +104,10 @@ internal class BotNetworkHandlerImpl(private val bot: Bot) : BotNetworkHandler {
|
||||
return
|
||||
}
|
||||
|
||||
temporaryPacketHandlers.removeIf {
|
||||
it.onPacketReceived(action.session, packet)
|
||||
}
|
||||
|
||||
//For debug
|
||||
kotlin.run {
|
||||
if (!packet.javaClass.name.endsWith("Encrypted") && !packet.javaClass.name.endsWith("Raw")) {
|
||||
@ -108,7 +115,7 @@ internal class BotNetworkHandlerImpl(private val bot: Bot) : BotNetworkHandler {
|
||||
}
|
||||
|
||||
if (packet is ServerEventPacket) {
|
||||
sendPacket(ClientMessageResponsePacket(bot.account.qqNumber, packet.packetId, sessionKey, packet.eventIdentity))
|
||||
sendPacket(ClientEventResponsePacket(bot.account.qqNumber, packet.packetId, sessionKey, packet.eventIdentity))
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,6 +168,7 @@ internal class BotNetworkHandlerImpl(private val bot: Bot) : BotNetworkHandler {
|
||||
}.start()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start network and touch the server
|
||||
*/
|
||||
@ -173,7 +181,7 @@ internal class BotNetworkHandlerImpl(private val bot: Bot) : BotNetworkHandler {
|
||||
this.loginFuture = CompletableFuture()
|
||||
|
||||
serverIP = serverAddress
|
||||
waitForPacket(ServerPacket::class, timeoutMillis) {
|
||||
bot.waitForPacket(ServerPacket::class, timeoutMillis) {
|
||||
loginFuture!!.complete(LoginState.TIMEOUT)
|
||||
}
|
||||
sendPacket(ClientTouchPacket(bot.account.qqNumber, serverIP))
|
||||
@ -209,30 +217,7 @@ internal class BotNetworkHandlerImpl(private val bot: Bot) : BotNetworkHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@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 getOwner(): Bot = this@BotNetworkHandlerImpl.bot
|
||||
|
||||
override fun close() {
|
||||
this.socket?.close()
|
||||
|
@ -2,7 +2,11 @@ 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.
|
||||
@ -15,11 +19,80 @@ class LoginSession(
|
||||
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,9 +1,14 @@
|
||||
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
|
||||
|
||||
/**
|
||||
* 网络接口.
|
||||
@ -13,12 +18,35 @@ import java.io.Closeable
|
||||
* @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()
|
||||
|
@ -4,13 +4,16 @@ 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.Message
|
||||
import net.mamoe.mirai.message.defaults.MessageChain
|
||||
import net.mamoe.mirai.message.defaults.PlainText
|
||||
import net.mamoe.mirai.message.defaults.UnsolvedImage
|
||||
import net.mamoe.mirai.network.LoginSession
|
||||
import net.mamoe.mirai.network.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
|
||||
|
||||
/**
|
||||
* 处理消息事件, 承担消息发送任务.
|
||||
@ -27,10 +30,18 @@ class MessagePacketHandler(session: LoginSession) : PacketHandler(session) {
|
||||
if (session.socket.isClosed()) {
|
||||
return@hookWhile false
|
||||
}
|
||||
if (it.message() valueEquals "你好") {
|
||||
it.qq.sendMessage("你好!")
|
||||
} else if (it.message().toString().startsWith("复读")) {
|
||||
it.qq.sendMessage(it.message())
|
||||
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
|
||||
@ -71,11 +82,10 @@ class MessagePacketHandler(session: LoginSession) : PacketHandler(session) {
|
||||
}
|
||||
|
||||
fun sendFriendMessage(qq: QQ, message: MessageChain) {
|
||||
session.socket.sendPacket(ClientSendFriendMessagePacket(session.bot.account.qqNumber, qq.number, session.sessionKey, message))
|
||||
session.socket.sendPacketAsync(ClientSendFriendMessagePacket(session.bot.account.qqNumber, qq.number, session.sessionKey, message))
|
||||
}
|
||||
|
||||
fun sendGroupMessage(group: Group, message: Message) {
|
||||
TODO()
|
||||
//sendPacket(ClientSendGroupMessagePacket(group.groupId, bot.account.qqNumber, sessionKey, message))
|
||||
fun sendGroupMessage(group: Group, message: MessageChain) {
|
||||
session.socket.sendPacket(ClientSendGroupMessagePacket(group.groupId, session.bot.account.qqNumber, session.sessionKey, message))
|
||||
}
|
||||
}
|
@ -2,9 +2,8 @@ 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
|
||||
import java.util.*
|
||||
import kotlin.NoSuchElementException
|
||||
|
||||
/**
|
||||
* 数据包(接受/发送)处理器
|
||||
@ -28,7 +27,7 @@ fun PacketHandler.asNode(): PacketHandlerNode<PacketHandler> {
|
||||
return PacketHandlerNode(this.javaClass, this)
|
||||
}
|
||||
|
||||
class PacketHandlerList : LinkedList<PacketHandlerNode<*>>() {
|
||||
class PacketHandlerList : MiraiSynchronizedLinkedList<PacketHandlerNode<*>>() {
|
||||
|
||||
fun <T : PacketHandler> get(clazz: Class<T>): T {
|
||||
this.forEach {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -217,7 +217,6 @@ fun DataOutputStream.writeQQ(qq: Long) {
|
||||
this.write(qq.toUInt().toByteArray())
|
||||
}
|
||||
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun DataOutputStream.writeGroup(groupIdOrGroupNumber: Long) {
|
||||
this.write(groupIdOrGroupNumber.toUInt().toByteArray())
|
||||
@ -230,4 +229,8 @@ fun DataOutputStream.writeLVByteArray(byteArray: ByteArray) {
|
||||
|
||||
fun DataOutputStream.writeLVString(str: String) {
|
||||
this.writeLVByteArray(str.toByteArray())
|
||||
}
|
||||
|
||||
fun DataOutputStream.writeLVHex(hex: String) {
|
||||
this.writeLVByteArray(hex.hexToBytes())
|
||||
}
|
@ -105,6 +105,7 @@ class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray,
|
||||
|
||||
|
||||
override fun decode() {
|
||||
println(this.input.goto(0).readAllBytes().toUHexString())
|
||||
groupNumber = this.input.goto(51).readInt().toLong()
|
||||
qq = this.input.goto(56).readLong()
|
||||
val fontLength = this.input.goto(108).readShort()
|
||||
@ -225,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
|
||||
}
|
||||
@ -294,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("")//随后写入
|
||||
|
||||
class ClientMessageResponsePacket(
|
||||
class ClientEventResponsePacket(
|
||||
private val qq: Long,
|
||||
private val packetIdFromServer: ByteArray,//4bytes
|
||||
private val sessionKey: ByteArray,
|
||||
|
@ -4,14 +4,20 @@
|
||||
|
||||
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
|
||||
@ -76,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
|
||||
@ -115,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 }
|
||||
@ -223,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()
|
||||
@ -288,6 +296,32 @@ fun DataInputStream.gotoWhere(matcher: ByteArray): DataInputStream {
|
||||
} 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) {
|
||||
|
@ -17,7 +17,7 @@ import java.io.IOException
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||
@Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
|
||||
@PacketId("08 25 31 01")
|
||||
class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inputStream) {
|
||||
var serverIP: String? = null
|
||||
@ -33,7 +33,7 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp
|
||||
|
||||
|
||||
override fun decode() {
|
||||
when (val id = input.readByte().toInt()) {
|
||||
when (val id = input.readByte().toUByte().toInt()) {
|
||||
0xFE -> {
|
||||
input.skip(94)
|
||||
serverIP = input.readIP()
|
||||
|
@ -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")
|
||||
|
||||
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())
|
||||
}
|
||||
|
@ -17,17 +17,16 @@ import java.io.DataInputStream
|
||||
* @author Him188moe
|
||||
*/
|
||||
@PacketId("03 88")
|
||||
|
||||
class ClientTryGetGroupImageIDPacket(
|
||||
private val bot: Long,
|
||||
class ClientTryGetImageIDPacket(
|
||||
private val botNumber: Long,
|
||||
private val sessionKey: ByteArray,
|
||||
private val group: Long,
|
||||
private val groupNumberOrQQNumber: Long,//todo 为什么还要有qq number呢? bot不就是了么
|
||||
private val image: BufferedImage
|
||||
) : ClientPacket() {
|
||||
override fun encode() {
|
||||
this.writeRandom(2)
|
||||
|
||||
this.writeQQ(bot)
|
||||
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()
|
||||
@ -46,10 +45,10 @@ class ClientTryGetGroupImageIDPacket(
|
||||
it.writeHex("5A")
|
||||
|
||||
it.writeHex("08")
|
||||
it.writeUVarInt(group)
|
||||
it.writeUVarInt(groupNumberOrQQNumber)
|
||||
|
||||
it.writeHex("10")
|
||||
it.writeUVarInt(bot)
|
||||
it.writeUVarInt(botNumber)
|
||||
|
||||
it.writeHex("18 00")
|
||||
|
||||
@ -77,7 +76,7 @@ class ClientTryGetGroupImageIDPacket(
|
||||
|
||||
it.writeHex("6A")
|
||||
it.writeHex("05")
|
||||
it.writeHex("32 36 36 35 36 05")//6?
|
||||
it.writeHex("32 36 36 35 36")
|
||||
|
||||
it.writeHex("70 00")
|
||||
|
||||
@ -95,7 +94,9 @@ abstract class ServerTryUploadGroupImageResponsePacket(input: DataInputStream) :
|
||||
class Encrypted(input: DataInputStream) : ServerPacket(input) {
|
||||
fun decrypt(sessionKey: ByteArray): ServerTryUploadGroupImageResponsePacket {
|
||||
val data = this.decryptAsByteArray(sessionKey)
|
||||
if (data.size == 239) {
|
||||
println(data.size)
|
||||
println(data.size)
|
||||
if (data.size == 209) {
|
||||
return ServerTryUploadGroupImageSuccessPacket(data.dataInputStream()).setId(this.idHex)
|
||||
}
|
||||
|
||||
|
@ -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,32 +1,31 @@
|
||||
package net.mamoe.mirai.utils;
|
||||
|
||||
import org.apache.commons.httpclient.util.HttpURLConnection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public class ImageNetworkUtils {
|
||||
public static void postImage(String uKeyHex, int fileSize, String qqNumber, String groupCode, byte[] img) throws IOException {
|
||||
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(uKeyHex.trim())
|
||||
.append("&filezise=").append(fileSize)
|
||||
.append("&range=").append("0")
|
||||
.append("&uin=").append(qqNumber)
|
||||
.append("&groupcode=").append(groupCode);
|
||||
|
||||
HttpURLConnection conn = (HttpURLConnection) new URL(builder.toString()).openConnection();
|
||||
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());
|
||||
System.out.println(conn.getResponseMessage());
|
||||
return conn.getResponseCode() == 200;
|
||||
}
|
||||
}
|
||||
|
@ -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,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();
|
||||
|
Loading…
Reference in New Issue
Block a user