send image

This commit is contained in:
Him188moe 2019-09-13 18:59:05 +08:00
parent 37fa8055b5
commit ff0acc87e3
30 changed files with 662 additions and 207 deletions

View File

@ -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>

View File

@ -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() {

View File

@ -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;
}

View File

@ -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)

View File

@ -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

View File

@ -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),

View File

@ -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()

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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")
}
}
}

View File

@ -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()
}

View File

@ -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()

View File

@ -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
}
}

View File

@ -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()

View File

@ -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))
}
}

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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())
}

View File

@ -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,

View File

@ -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) {

View File

@ -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()

View File

@ -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())
/*

View File

@ -1,5 +1,6 @@
package net.mamoe.mirai.network.packet.action
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.network.packet.*
import net.mamoe.mirai.utils.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())
}

View File

@ -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)
}

View File

@ -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> {
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();