This commit is contained in:
Him188 2019-11-15 12:14:15 +08:00
parent 0717ff77a9
commit 046d8968a4
17 changed files with 73 additions and 76 deletions

View File

@ -25,7 +25,7 @@ actual val deviceName: String get() = InetAddress.getLocalHost().hostName
* Ktor HttpClient. 不同平台使用不同引擎.
*/
@KtorExperimentalAPI
internal actual val httpClient: HttpClient
internal actual val Http: HttpClient
get() = HttpClient(CIO)
/**

View File

@ -226,6 +226,7 @@ Mirai 22:04:48 : Packet received: UnknownEventPacket(id=00 D6, identity=(2092749
* 添加一个好友
*
* @param lazyMessage 若需要验证请求时的验证消息.
* @param lazyRemark 好友备注
*/
suspend fun ContactSystem.addFriend(id: UInt, lazyMessage: () -> String = { "" }, lazyRemark: () -> String = { "" }): AddFriendResult = bot.withSession {
when (CanAddFriendPacket(bot.qqAccount, id, bot.sessionKey).sendAndExpect<CanAddFriendResponse>()) {

View File

@ -5,7 +5,9 @@ package net.mamoe.mirai.message
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImageIdRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.download
import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.Http
import kotlin.js.JsName
import kotlin.jvm.Volatile
@ -154,17 +156,6 @@ inline class Image(inline val id: ImageId) : Message {
override val stringValue: String get() = "[${id.value}]"
override fun toString(): String = stringValue
/**
* 下载这个图片
*/
suspend fun download(): ExternalImage {
// http://101.89.39.21/offpic_new/892185277C8A24AB9BB91AE888A6BC910C4D48CBA84BBB2F0D800761F1DD2F71F205364442C451A82D2F6FAD38CF71A9D65B6873409BD10CCF22BBF7DFA73DD7DA06FBB7386E31E4/0?vuin=1040400290&term=255&srvver=26933&rf=naio
// http://61.151.234.54/offpic_new/A47CAC5D3C5EF955A77ECE13DA969A69238167886464C00FD75FFC49A76EF15A1F05ED04BC2DE91190A40048AAF97BB6DFDB25BFB0EFE6A9516DC3BE0532A9A93A87A4C8D2E9729C/0?vuin=1040400290&term=255&srvver=26933&rf=naio
TODO()
}
companion object Key : Message.Key<Image>
}
@ -181,7 +172,11 @@ inline class ImageId(inline val value: String)
/**
* 图片数据地址.
*/
inline class ImageLink(inline val value: String)
inline class ImageLink(inline val value: String) {
// TODO: 2019/11/15 应添加更多方法. 避免使用 byte[]
suspend fun downloadAsByteArray(): ByteArray = Http.download(value)
}
fun ImageId.checkLength() = check(value.length == 37 || value.length == 42) { "Illegal ImageId length" }
fun ImageId.requireLength() = require(value.length == 37 || value.length == 42) { "Illegal ImageId length" }

View File

@ -31,7 +31,6 @@ import kotlin.coroutines.coroutineContext
*/
@Suppress("FunctionName", "NOTHING_TO_INLINE")
internal inline fun TIMBotNetworkHandler.BotSession(
bot: Bot,
sessionKey: SessionKey,
socket: DataPacketSocketAdapter
): BotSession = BotSession(bot, sessionKey, socket, this)
@ -42,7 +41,7 @@ internal inline fun TIMBotNetworkHandler.BotSession(
*
* @author Him188moe
*/
class BotSession(
data class BotSession(
val bot: Bot,
val sessionKey: SessionKey,
val socket: DataPacketSocketAdapter,
@ -105,39 +104,11 @@ class BotSession(
return deferred
}
/**
* 发送一个数据包, 并期待接受一个特定的 [ServerPacket][P].
* 将能从本函数的返回值 [CompletableDeferred] 接收到所期待的 [P]
* 本函数将能帮助实现清晰的包处理逻辑
*
* 实现方法:
* ```kotlin
* with(session){
* val deferred = ClientPacketXXX(...).sendAndExpectAsync<ServerPacketXXX>()
* // do something
*
* deferred.await() // or deferred.join()
* }
* ```
*/
suspend inline fun <reified P : Packet> OutgoingPacket.sendAndExpectAsync(checkSequence: Boolean = true): Deferred<P> =
sendAndExpectAsync<P, P>(checkSequence) { it }
/**
* 发送一个数据包, 并期待接受一个特定的 [ServerPacket][P].
* 将调用 [CompletableDeferred.await], 挂起等待接收到指定的包后才返回.
* 本函数将能帮助实现清晰的包处理逻辑
*
* 实现方法:
* ```kotlin
* with(session){
* val packet:AddFriendPacket.Response = AddFriendPacket(...).sendAndExpect<AddFriendPacket.Response>()
* }
* ```
* @sample Bot.addFriend 添加好友
*/
suspend inline fun <reified P : Packet, R> OutgoingPacket.sendAndExpect(checkSequence: Boolean = true, crossinline block: (P) -> R): R =
sendAndExpectAsync<P, R>(checkSequence) { block(it) }.await()
suspend inline fun <reified P : Packet, R> OutgoingPacket.sendAndExpect(checkSequence: Boolean = true, crossinline mapper: (P) -> R): R =
sendAndExpectAsync<P, R>(checkSequence) { mapper(it) }.await()
suspend inline fun <reified P : Packet> OutgoingPacket.sendAndExpect(checkSequence: Boolean = true): P =
sendAndExpectAsync<P, P>(checkSequence) { it }.await()

View File

@ -62,7 +62,7 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
handlersLock.withLock {
temporaryPacketHandlers.add(temporaryPacketHandler)
}
temporaryPacketHandler.send(this[ActionPacketHandler].session)
temporaryPacketHandler.send(this.session)
}
override suspend fun login(configuration: BotConfiguration): LoginResult {
@ -90,7 +90,7 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
//private | internal
private fun onLoggedIn() {
require(size == 0) { "Already logged in" }
val session = BotSession(bot, sessionKey, socket)
val session = BotSession(sessionKey, socket)
add(ActionPacketHandler(session).asNode(ActionPacketHandler))
bot.logger.info("Successfully logged in")

View File

@ -36,5 +36,5 @@ internal fun <T : PacketHandler> T.asNode(key: PacketHandler.Key<T>): PacketHand
internal open class PacketHandlerList : MutableList<PacketHandlerNode<*>> by mutableListOf() {
@Suppress("UNCHECKED_CAST")
operator fun <T : PacketHandler> get(key: PacketHandler.Key<T>): T = this.first { it.key === key }.instance as T
operator fun <T : PacketHandler> get(key: PacketHandler.Key<T>): T = this.first { it.key == key }.instance as T
}

View File

@ -9,11 +9,10 @@ import kotlin.reflect.KClass
/**
* ID. 除特殊外, [PacketFactory] 都需要这个注解来指定包 ID.
*/
@Deprecated("Reflection is not supported in JS. Consider to remove")
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class AnnotatedId(
annotation class AnnotatedId( // 注解无法在 JS 平台使用, 但现在暂不需要考虑 JS
val id: KnownPacketId
)
@ -40,7 +39,7 @@ annotation class CorrespondingEvent(
internal annotation class PacketVersion(val date: String, val timVersion: String)
/**
* 带有这个注解的 [Packet], 将不会被记录在 log .
* 带有这个注解的 [Packet] 将不会被记录在 log .
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)

View File

@ -26,7 +26,7 @@ abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypter>(val d
*/
private val annotatedId: AnnotatedId
get() = (this::class.annotations.firstOrNull { it is AnnotatedId } as? AnnotatedId)
?: error("Annotation AnnotatedId not found")
?: error("Annotation AnnotatedId not found for class ${this::class.simpleName}")
/**
* ID.

View File

@ -205,7 +205,7 @@ object AddFriendPacket : SessionPacketFactory<AddFriendPacket.Response>() {
/**
* 备注名
*/
remark: String,
remark: String, //// TODO: 2019/11/15 无备注的情况
key: FriendAdditionKey
): OutgoingPacket = buildSessionPacket(bot, sessionKey) {

View File

@ -0,0 +1,16 @@
package net.mamoe.mirai.network.protocol.tim.packet.action
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.withSession
import net.mamoe.mirai.message.Image
import net.mamoe.mirai.message.ImageLink
import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImageIdDownloadLinkRequestPacket.ImageLinkResponse
import net.mamoe.mirai.network.sessionKey
import net.mamoe.mirai.qqAccount
suspend fun QQ.getLink(image: Image): ImageLink = withSession {
FriendImageIdDownloadLinkRequestPacket(bot.qqAccount, bot.sessionKey, id, image.id).sendAndExpect<ImageLinkResponse>().link
}
suspend inline fun QQ.downloadAsByteArray(image: Image): ByteArray = this.getLink(image).downloadAsByteArray()

View File

@ -12,6 +12,8 @@ import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.io.*
@AnnotatedId(KnownPacketId.SEND_GROUP_MESSAGE)
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)")
object SendGroupMessagePacket : SessionPacketFactory<SendGroupMessagePacket.Response>() {
operator fun invoke(
botQQ: UInt,

View File

@ -14,6 +14,7 @@ import kotlinx.coroutines.withContext
import kotlinx.io.core.*
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.message.ImageId
import net.mamoe.mirai.message.ImageLink
import net.mamoe.mirai.message.requireLength
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
@ -22,8 +23,8 @@ import net.mamoe.mirai.network.protocol.tim.packet.event.EventPacket
import net.mamoe.mirai.network.qqAccount
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.Http
import net.mamoe.mirai.utils.configureBody
import net.mamoe.mirai.utils.httpClient
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.withSession
import kotlin.coroutines.coroutineContext
@ -47,7 +48,7 @@ suspend fun Group.uploadImage(image: ExternalImage): ImageId = withSession {
withContext(userContext) {
when (response) {
is GroupImageIdRequestPacket.Response.RequireUpload -> httpClient.postImage(
is GroupImageIdRequestPacket.Response.RequireUpload -> Http.postImage(
htcmd = "0x6ff0071",
uin = bot.qqAccount,
groupId = GroupId(id),
@ -79,7 +80,7 @@ suspend fun QQ.uploadImage(image: ExternalImage): ImageId = bot.withSession {
.sendAndExpectAsync<FriendImageIdRequestPacket.Response, ImageId> {
return@sendAndExpectAsync when (it) {
is FriendImageIdRequestPacket.Response.RequireUpload -> {
httpClient.postImage(
Http.postImage(
htcmd = "0x6ff0070",
uin = bot.qqAccount,
groupId = null,
@ -132,11 +133,11 @@ internal suspend inline fun HttpClient.postImage(
imageInput.close()
}
internal suspend inline fun HttpClient.download(
url: String
): ByteArray = get<HttpResponse>(url).readBytes() // TODO 不知道为什么找不到 `ByteReadChannel`.
/*
/**
* 似乎没有必要. 服务器的返回永远都是 01 00 00 00 02 00 00
@ -195,7 +196,7 @@ object SubmitImageFilenamePacket : PacketFactory {
*/
@AnnotatedId(KnownPacketId.FRIEND_IMAGE_ID)
@PacketVersion(date = "2019.11.14", timVersion = "2.3.2 (21173)")
object FriendImageIdDownloadLinkRequestPacket : SessionPacketFactory<FriendImageIdDownloadLinkRequestPacket.DownloadLink>() {
object FriendImageIdDownloadLinkRequestPacket : SessionPacketFactory<FriendImageIdDownloadLinkRequestPacket.ImageLinkResponse>() {
operator fun invoke(
bot: UInt,
sessionKey: SessionKey,
@ -239,14 +240,14 @@ object FriendImageIdDownloadLinkRequestPacket : SessionPacketFactory<FriendImage
/**
* 图片下载链接. 直接 'get' 请求即可
*/
data class DownloadLink(
data class ImageLinkResponse(
val imageId: ImageId,
val link: String
val link: ImageLink
) : Packet
// TODO: 2019/11/14 需要跟 RequestId packet 合并. 因为现行结构无法分别处理; 或者考虑修改结构(不推荐)
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): DownloadLink {
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): ImageLinkResponse {
//00 00 00 08 00 00
// [02 2B]
// 12 [06] 98 01 02 A0 01 00
@ -282,7 +283,7 @@ object FriendImageIdDownloadLinkRequestPacket : SessionPacketFactory<FriendImage
val link = readUVarIntLVString()
discard()
return DownloadLink(imageId, link)
return ImageLinkResponse(imageId, ImageLink(link))
}
}

View File

@ -147,7 +147,7 @@ object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse,
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): LoginResponse {
val size = remaining.toInt()
return when {
size == 229 || size == 271 || size == 207 -> {
size == 229 || size == 271 || size == 207 || size == 165 /* TODO CHECK 165 */ -> {
discardExact(5)//01 00 1E 00 10
val privateKeyUpdate = PrivateKey(readBytes(0x10))
discardExact(4)//00 06 00 78
@ -243,6 +243,8 @@ object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse,
279, 495, 551, 487 -> LoginResult.DEVICE_LOCK
343, 359 -> LoginResult.TAKEN_BACK
// 165: 01 00 1E 00 10 72 36 7B 6B 6D 78 3A 4B 63 7B 47 5B 68 3E 21 59 00 06 00 78 34 F6 F9 49 AA 13 F5 F5 01 36 13 E1 4C F7 0F 25 C1 2C 10 75 CA 69 E9 12 B3 6D F4 A7 59 60 FF 01 03 73 28 47 A3 2A B8 46 C3 92 24 D5 8A AE 8B C2 45 0C 31 27 B5 17 9E 22 13 59 AF B4 CC F6 E3 3A 91 60 13 21 11 3C 25 D9 50 F4 23 C6 06 1D F4 15 41 BA 5D 7B 66 26 96 EB 0E 04 14 8E 5B D4 33 6E B8 5D E7 10 3A 0E EF 96 B1 D4 22 E4 74 48 A7 1D 3A 46 7D E6 EF 1F 6B 69 01 15 00 10 6F 99 48 5E 98 AE D3 4B F8 35 63 1D 70 EE 6D 82
else -> {
MiraiLogger.error("login response packet size = $size, data=${this.readRemainingBytes().toUHexString()}")
LoginResult.UNKNOWN

View File

@ -43,7 +43,7 @@ expect fun localIpAddress(): String
/**
* Ktor HttpClient. 不同平台使用不同引擎.
*/
internal expect val httpClient: HttpClient
internal expect val Http: HttpClient
// FIXME: 2019/10/28 这个方法不是很好的实现

View File

@ -55,7 +55,7 @@ actual fun solveIpAddress(hostname: String): String = InetAddress.getByName(host
actual fun localIpAddress(): String = InetAddress.getLocalHost().hostAddress
internal actual val httpClient: HttpClient = HttpClient(CIO)
internal actual val Http: HttpClient = HttpClient(CIO)
internal actual fun HttpRequestBuilder.configureBody(
inputSize: Long,

View File

@ -9,6 +9,8 @@ dependencies {
api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlin_version
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutines_version
implementation("org.jetbrains.kotlinx:kotlinx-io:$kotlinxio_version")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-io:$coroutinesio_version")
compile group: 'com.alibaba', name: 'fastjson', version: '1.2.62'
implementation 'org.jsoup:jsoup:1.12.1'
}

View File

@ -2,9 +2,11 @@
package demo.gentleman
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.mamoe.mirai.Bot
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.addFriend
@ -12,7 +14,11 @@ import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.login
import net.mamoe.mirai.message.Image
import net.mamoe.mirai.network.protocol.tim.packet.action.downloadAsByteArray
import net.mamoe.mirai.network.protocol.tim.packet.event.FriendMessage
import net.mamoe.mirai.network.protocol.tim.packet.login.requireSuccess
import net.mamoe.mirai.utils.currentTime
import java.io.File
import kotlin.random.Random
@ -52,21 +58,23 @@ suspend fun main() {
sender.profile.await().toString()
}
/*
has<Image> {
reply(message)
}*/
if (this is FriendMessage) {
withContext(IO) {
reply(message[Image] + " downloading")
sender.downloadAsByteArray(message[Image]).inputStream()
.transferTo(File(System.getProperty("user.dir", "testDownloadedImage${currentTime}.png")).outputStream())
reply(message[Image] + " downloaded")
}
}
}
startsWith("随机图片", removePrefix = true) {
try {
repeat(it.toIntOrNull() ?: 1) {
GlobalScope.launch {
delay(Random.Default.nextLong(100, 1000))
Gentlemen.provide(subject).receive().image.await().send()
}
repeat(it.toIntOrNull() ?: 1) {
GlobalScope.launch {
delay(Random.Default.nextLong(100, 1000))
Gentlemen.provide(subject).receive().image.await().send()
}
} catch (e: Exception) {
reply(e.message ?: "exception: null")
}
}