Commit ad710766 authored by jiahua.liu's avatar jiahua.liu

Merge remote-tracking branch 'origin/master'

parents 5c04ba44 76450c87
......@@ -3,12 +3,11 @@
<b>
Mirai-API-http 提供HTTP API供所有语言使用mirai<br>
</b>
### 开始会话-认证(Authorize)
```php
路径: /auth
方法: POST
```
[POST] /auth
```
使用此方法验证你的会话连接, 并将这个会话绑定一个BOT<br>
注意: 每个会话只能绑定一个BOT.
......@@ -20,36 +19,34 @@ Mirai-API-http 提供HTTP API供所有语言使用mirai<br>
| key | String |false|U9HSaDXl39ksd918273hU|MIRAI API HTTP key, HTTP API的核心key|
| qq | String |false|1040400290|需要绑定的BOT QQ号|
#### 返回(成功):<br>
| 名字 | 类型 | 举例 | 说明|
| --- | --- | --- | --- |
| success |Boolean |true|是否验证成功|
| code |Int |0|返回状态|
| session |String |UANSHDKSLAOISN|你的session key|
#### 返回(失败):<br>
| name | type | example|note|
| --- | --- | --- | --- |
| success |Boolean |false|是否验证成功|
| session |String |null|你的session key|
| error |int |0|错误码|
#### 错误码:<br>
#### 状态码:<br>
| 代码 | 原因|
| --- | --- |
| 0 | 错误的MIRAI API HTTP key |
| 1 | 试图绑定不存在的bot|
| 0 | 正常 |
| 1 | 错误的MIRAI API HTTP key|
| 2 | 试图绑定不存在的bot|
session key 是使用以下方法必须携带的</br>
session key 需要被以cookie的形式上报 <b>cookies</b> :
| name | value |
| --- | --- |
| session |your session key here |
如果出现HTTP 403错误码,代表session key已过期, 需要重新获取
\ No newline at end of file
| 名字 | 值 |
| --- | --- |
| session |your session key here |
如果出现HTTP 403错误码,代表session key已过期, 需要重新获取
### 发送好友消息
```
[POST] /sendFriendMessage
```
......@@ -42,6 +42,7 @@ kotlin {
implementation(ktor("server-cio"))
implementation(kotlinx("io-jvm", kotlinXIoVersion))
implementation(ktor("http-jvm"))
implementation("org.slf4j:slf4j-simple:1.7.26")
}
}
......
package net.mamoe.mirai.api.http
import io.ktor.application.Application
import io.ktor.server.cio.CIO
import io.ktor.server.engine.embeddedServer
import io.ktor.util.KtorExperimentalAPI
import net.mamoe.mirai.api.http.route.mirai
import net.mamoe.mirai.utils.DefaultLogger
object MiraiHttpAPIServer {
private val logger = DefaultLogger("Mirai HTTP API")
init {
SessionManager.authKey = generateSessionKey()//用于验证的key, 使用和SessionKey相同的方法生成, 但意义不同
}
@UseExperimental(KtorExperimentalAPI::class)
fun start(
port: Int = 8080,
authKey: String? = null,
callback: (() -> Unit)? = null
) {
authKey?.apply {
if (authKey.length in 8..128) {
SessionManager.authKey = authKey
} else {
logger.error("Expected authKey length is between 8 to 128")
}
}
// TODO: start是无阻塞的,理应获取启动状态后再执行后续代码
try {
embeddedServer(CIO, port, module = Application::mirai).start()
logger.info("Http api server is running with authKey: ${SessionManager.authKey}")
callback?.invoke()
} catch (e: Exception) {
logger.error("Http api server launch error")
}
}
}
\ No newline at end of file
package net.mamoe.mirai.api.http
import kotlinx.coroutines.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import java.lang.StringBuilder
import net.mamoe.mirai.Bot
import net.mamoe.mirai.api.http.queue.MessageQueue
import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.message.MessagePacket
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
......@@ -44,6 +46,10 @@ object SessionManager {
}
}
operator fun get(sessionKey: String) = allSession[sessionKey]
fun containSession(sessionKey: String): Boolean = allSession.containsKey(sessionKey)
fun closeSession(sessionKey: String) = allSession.remove(sessionKey)?.also {it.close() }
fun closeSession(session: Session) = closeSession(session.key)
......@@ -69,7 +75,7 @@ abstract class Session internal constructor(
val key:String = generateSessionKey()
internal fun close(){
internal open fun close(){
supervisorJob.complete()
}
}
......@@ -81,19 +87,26 @@ abstract class Session internal constructor(
*
* TempSession在建立180s内没有转变为[AuthedSession]应被清除
*/
class TempSession internal constructor(coroutineContext: CoroutineContext) : Session(coroutineContext) {
}
class TempSession internal constructor(coroutineContext: CoroutineContext) : Session(coroutineContext)
/**
* 任何[TempSession]认证后转化为一个[AuthedSession]
* 在这一步[AuthedSession]应该已经有assigned的bot
*/
class AuthedSession internal constructor(val botNumber:Int, coroutineContext: CoroutineContext):Session(coroutineContext){
}
class AuthedSession internal constructor(val bot: Bot, coroutineContext: CoroutineContext):Session(coroutineContext){
val messageQueue = MessageQueue()
private val _listener : Listener<MessagePacket<*, *>>
init {
bot.subscribeMessages {
_listener = always { this.run(messageQueue::add) } // this aka messagePacket
}
}
override fun close() {
_listener.complete()
super.close()
}
}
package net.mamoe.mirai.api.http.dto
import kotlinx.serialization.Serializable
@Serializable
data class AuthDTO(val authKey: String) : DTO
package net.mamoe.mirai.api.http.dto
import kotlinx.serialization.Serializable
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.contact.QQ
@Serializable
abstract class ContactDTO : DTO {
abstract val id: Long
}
@Serializable
data class QQDTO(
override val id: Long,
val nickName: String,
val remark: String
) : ContactDTO()
suspend fun QQDTO(qq: QQ): QQDTO = QQDTO(qq.id, qq.queryProfile().nickname, qq.queryRemark().value)
@Serializable
data class MemberDTO(
override val id: Long,
val memberName: String = "",
val group: GroupDTO,
val permission: MemberPermission
) : ContactDTO()
fun MemberDTO(member: Member, name: String = ""): MemberDTO = MemberDTO(member.id, name, GroupDTO(member.group), member.permission)
@Serializable
data class GroupDTO(
override val id: Long,
val name: String
) : ContactDTO()
fun GroupDTO(group: Group): GroupDTO = GroupDTO(group.id, group.name)
\ No newline at end of file
package net.mamoe.mirai.api.http.dto
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
interface DTO
// 解析失败时直接返回null,由路由判断响应400状态
@UseExperimental(ImplicitReflectionSerializer::class)
inline fun <reified T : Any> String.jsonParseOrNull(
serializer: DeserializationStrategy<T>? = null
): T? = try {
if(serializer == null) MiraiJson.json.parse(this) else Json.parse(this)
} catch (e: Exception) { null }
@UseExperimental(ImplicitReflectionSerializer::class, UnstableDefault::class)
inline fun <reified T : Any> T.toJson(
serializer: SerializationStrategy<T>? = null
): String = if (serializer == null) MiraiJson.json.stringify(this)
else MiraiJson.json.stringify(serializer, this)
// 序列化列表时,stringify需要使用的泛型是T,而非List<T>
// 因为使用的stringify的stringify(objs: List<T>)重载
@UseExperimental(ImplicitReflectionSerializer::class, UnstableDefault::class)
inline fun <reified T : Any> List<T>.toJson(
serializer: SerializationStrategy<List<T>>? = null
): String = if (serializer == null) MiraiJson.json.stringify(this)
else MiraiJson.json.stringify(serializer, this)
/**
* Json解析规则,需要注册支持的多态的类
*/
object MiraiJson {
val json = Json(context = SerializersModule {
polymorphic(MessagePacketDTO.serializer()) {
GroupMessagePacketDTO::class with GroupMessagePacketDTO.serializer()
FriendMessagePacketDTO::class with FriendMessagePacketDTO.serializer()
UnKnownMessagePacketDTO::class with UnKnownMessagePacketDTO.serializer()
}
polymorphic(MessageDTO.serializer()) {
AtDTO::class with AtDTO.serializer()
FaceDTO::class with FaceDTO.serializer()
PlainDTO::class with PlainDTO.serializer()
ImageDTO::class with ImageDTO.serializer()
XmlDTO::class with XmlDTO.serializer()
UnknownMessageDTO::class with UnknownMessageDTO.serializer()
}
})
}
\ No newline at end of file
package net.mamoe.mirai.api.http.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.MessagePacket
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.MiraiInternalAPI
/*
* DTO data class
* */
// MessagePacket
@Serializable
@SerialName("FriendMessage")
data class FriendMessagePacketDTO(val sender: QQDTO) : MessagePacketDTO()
@Serializable
@SerialName("GroupMessage")
data class GroupMessagePacketDTO(val sender: MemberDTO) : MessagePacketDTO()
@Serializable
@SerialName("UnKnownMessage")
data class UnKnownMessagePacketDTO(val msg: String) : MessagePacketDTO()
// Message
@Serializable
@SerialName("At")
data class AtDTO(val target: Long, val display: String) : MessageDTO()
@Serializable
@SerialName("Face")
data class FaceDTO(val faceID: Int) : MessageDTO()
@Serializable
@SerialName("Plain")
data class PlainDTO(val text: String) : MessageDTO()
@Serializable
@SerialName("Image")
data class ImageDTO(val path: String) : MessageDTO()
@Serializable
@SerialName("Xml")
data class XmlDTO(val xml: String) : MessageDTO()
@Serializable
@SerialName("Unknown")
data class UnknownMessageDTO(val text: String) : MessageDTO()
/*
* Abstract Class
* */
@Serializable
sealed class MessagePacketDTO : DTO {
lateinit var messageChain : MessageChainDTO
}
typealias MessageChainDTO = Array<MessageDTO>
@Serializable
sealed class MessageDTO : DTO
/*
Extend function
*/
suspend fun MessagePacket<*, *>.toDTO(): MessagePacketDTO = when (this) {
is FriendMessage -> FriendMessagePacketDTO(QQDTO(sender))
is GroupMessage -> GroupMessagePacketDTO(MemberDTO(sender, senderName))
else -> UnKnownMessagePacketDTO("UnKnown Message Packet")
}.apply { messageChain = Array(message.size){ message[it].toDTO() }}
fun MessageChainDTO.toMessageChain() =
MessageChain().apply { this@toMessageChain.forEach { add(it.toMessage()) } }
@UseExperimental(ExperimentalUnsignedTypes::class)
fun Message.toDTO() = when (this) {
is At -> AtDTO(target, display)
is Face -> FaceDTO(id.value.toInt())
is PlainText -> PlainDTO(stringValue)
is Image -> ImageDTO(this.toString())
is XMLMessage -> XmlDTO(stringValue)
else -> UnknownMessageDTO("未知消息类型")
}
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
fun MessageDTO.toMessage() = when (this) {
is AtDTO -> At(target, display)
is FaceDTO -> Face(FaceId(faceID.toUByte()))
is PlainDTO -> PlainText(text)
is ImageDTO -> PlainText("[暂时不支持图片]")
is XmlDTO -> XMLMessage(xml)
is UnknownMessageDTO -> PlainText("assert cannot reach")
}
package net.mamoe.mirai.api.http.dto
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.mamoe.mirai.api.http.AuthedSession
@Serializable
abstract class VerifyDTO : DTO {
abstract val sessionKey: String
@Transient
lateinit var session: AuthedSession // 反序列化验证后传入
}
@Serializable
data class BindDTO(override val sessionKey: String, val qq: Long) : VerifyDTO()
@Serializable
open class StateCode(val code: Int, var msg: String) {
object Success : StateCode(0, "success") // 成功
object NoBot : StateCode(2, "指定Bot不存在")
object IllegalSession : StateCode(3, "Session失效或不存在")
object NotVerifySession : StateCode(4, "Session未认证")
object NoElement : StateCode(5, "指定对象不存在")
// KS bug: 主构造器中不能有非字段参数 https://github.com/Kotlin/kotlinx.serialization/issues/575
@Serializable
class IllegalAccess() : StateCode(400, "") { // 非法访问
constructor(msg: String) : this() {
this.msg = msg
}
}
}
@Serializable
data class SendDTO(
override val sessionKey: String,
val target: Long,
val messageChain: MessageChainDTO
) : VerifyDTO()
package net.mamoe.mirai.api.http.queue
import net.mamoe.mirai.message.MessagePacket
import java.util.concurrent.ConcurrentLinkedDeque
import kotlin.collections.ArrayList
class MessageQueue : ConcurrentLinkedDeque<MessagePacket<*, *>>() {
fun fetch(size: Int): List<MessagePacket<*, *>> {
var count = size
val ret = ArrayList<MessagePacket<*, *>>(count)
while (!this.isEmpty() && count-- > 0) {
ret.add(this.pop())
}
return ret
}
}
\ No newline at end of file
package net.mamoe.mirai.api.http.route
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.routing.routing
import net.mamoe.mirai.Bot
import net.mamoe.mirai.api.http.AuthedSession
import net.mamoe.mirai.api.http.SessionManager
import net.mamoe.mirai.api.http.dto.*
import kotlin.coroutines.EmptyCoroutineContext
fun Application.authModule() {
routing {
miraiAuth("/auth") {
if (it.authKey != SessionManager.authKey) {
call.respondStateCode(StateCode(1, "Auth Key错误"))
} else {
call.respondStateCode(StateCode(0, SessionManager.createTempSession().key))
}
}
miraiVerify<BindDTO>("/verify", verifiedSessionKey = false) {
try {
val bot = Bot.instanceWhose(it.qq)
with(SessionManager) {
closeSession(it.sessionKey)
allSession[it.sessionKey] = AuthedSession(bot, EmptyCoroutineContext)
}
call.respondStateCode(StateCode.Success)
} catch (e: NoSuchElementException) {
call.respondStateCode(StateCode.NoBot)
}
}
miraiVerify<BindDTO>("/release") {
SessionManager.closeSession(it.sessionKey)
call.respondStateCode(StateCode.Success)
}
}
}
package net.mamoe.mirai.api.http
package net.mamoe.mirai.api.http.route
import io.ktor.application.Application
import io.ktor.application.ApplicationCall
......@@ -6,129 +6,131 @@ import io.ktor.application.call
import io.ktor.application.install
import io.ktor.features.CallLogging
import io.ktor.features.DefaultHeaders
import io.ktor.http.ContentType
import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
import io.ktor.response.respond
import io.ktor.request.receive
import io.ktor.response.defaultTextContentType
import io.ktor.response.respondText
import io.ktor.routing.Route
import io.ktor.routing.route
import io.ktor.routing.routing
import io.ktor.server.engine.applicationEngineEnvironment
import io.ktor.util.pipeline.ContextDsl
import io.ktor.util.pipeline.PipelineContext
import io.ktor.util.pipeline.PipelineInterceptor
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.hexToUBytes
fun main(args: Array<String>) {
val logger = DefaultLogger("Mirai HTTP API")
//write default first
SessionManager.authKey = generateSessionKey()//用于验证的key, 使用和SessionKey相同的方法生成, 但意义不同
var port = 8080//start port
args.forEach {
if(it.contains("=")) {
when {
it.toLowerCase().contains("authkey") -> {
SessionManager.authKey = it.split("=")[1].trim()
if(it.length !in 8..128){
logger.error("Expected authKey length is between 8 to 128")
SessionManager.authKey = generateSessionKey()
}
logger.info("Session Auth Key now is ${SessionManager.authKey}")
}
it.toLowerCase().contains("port") -> {
try {
port = it.split("=")[1].trim().toInt()
}catch (e:Exception){
logger.error("Expected -port=xxxxx, xxxxx to be numbers")
}
if(port !in 1025..65535){
logger.error("Expected -port=xxxxx, xxxxx > 1024 && <65536")
port = 8080
}
logger.info("HTTP API Listening port now is $port")
}
}
}
if(it.contains("help")){
logger.info("-authkey=XXXXXXXX to use custom Session Auth Key, note that key is case sensitive")
logger.info("-port=XXXXX to use custom listener port, default using 8080")
}
}
import net.mamoe.mirai.api.http.AuthedSession
import net.mamoe.mirai.api.http.SessionManager
import net.mamoe.mirai.api.http.TempSession
import net.mamoe.mirai.api.http.dto.*
Application(applicationEngineEnvironment {}).apply { mirai() }
}
@UseExperimental(ExperimentalUnsignedTypes::class)
fun Application.mirai() {
install(DefaultHeaders)
install(CallLogging)
routing {
mirai("/sendFriendMessage") {
// TODO: 2019/11/21 解析图片消息等为 Message
Bot.instanceWhose(qq = param("bot")).getFriend(param("qq")).sendMessage(param<String>("message"))
call.ok()
}
mirai("/sendGroupMessage") {
Bot.instanceWhose(qq = param("bot")).getGroup(param<Long>("group")).sendMessage(param<String>("message"))
call.ok()
}
mirai("/event/message") {
// TODO: 2019/11/21
Bot.instanceWhose(qq = param("bot"))
}
mirai("/addFriend") {
Bot.instanceWhose(qq = param("bot")).addFriend(
id = param("qq"),
message = paramOrNull("message"),
remark = paramOrNull("remark")
)
authModule()
messageModule()
}
call.ok()
/**
* Auth,处理http server的验证
* 为闭包传入一个AuthDTO对象
*/
@ContextDsl
internal fun Route.miraiAuth(
path: String,
body: suspend PipelineContext<Unit, ApplicationCall>.(AuthDTO) -> Unit
): Route {
return route(path, HttpMethod.Post) {
intercept {
val dto = context.receiveDTO<AuthDTO>() ?: throw IllegalParamException("参数格式错误")
this.body(dto)
}
}
}
/**
* Get,用于获取bot的属性
* 验证请求参数中sessionKey参数的有效性
*/
@ContextDsl
private fun Route.mirai(path: String, body: PipelineInterceptor<Unit, ApplicationCall>): Route {
internal fun Route.miraiGet(
path: String,
body: suspend PipelineContext<Unit, ApplicationCall>.(AuthedSession) -> Unit
): Route {
return route(path, HttpMethod.Get) {
handle {
try {
this.body(this.subject)
} catch (e: IllegalAccessException) {
call.respond(HttpStatusCode.BadRequest, e.message)
intercept {
val sessionKey = call.parameters["sessionKey"] ?: throw IllegalParamException("参数格式错误")
if (!SessionManager.containSession(sessionKey)) throw IllegalSessionException
when(val session = SessionManager[sessionKey]) {
is TempSession -> throw NotVerifiedSessionException
is AuthedSession -> this.body(session)
}
}
}
}
suspend inline fun ApplicationCall.ok() = this.respond(HttpStatusCode.OK, "OK")
/**
* 错误请求. 抛出这个异常后将会返回错误给一个请求
* Verify,用于处理bot的行为请求
* 验证数据传输对象(DTO)中是否包含sessionKey字段
* 且验证sessionKey的有效性
*
* @param verifiedSessionKey 是否验证sessionKey是否被激活
*
* it 为json解析出的DTO对象
*/
@Suppress("unused")
open class IllegalAccessException : Exception {
override val message: String get() = super.message!!
@ContextDsl
internal inline fun <reified T : VerifyDTO> Route.miraiVerify(
path: String,
verifiedSessionKey: Boolean = true,
crossinline body: suspend PipelineContext<Unit, ApplicationCall>.(T) -> Unit
): Route {
return route(path, HttpMethod.Post) {
intercept {
val dto = context.receiveDTO<T>() ?: throw IllegalParamException("参数格式错误")
SessionManager[dto.sessionKey]?.let {
when {
it is TempSession && verifiedSessionKey -> throw NotVerifiedSessionException
it is AuthedSession -> dto.session = it
}
} ?: throw IllegalSessionException
constructor(message: String) : super(message, null)
constructor(cause: Throwable) : super(cause.toString(), cause)
constructor(message: String, cause: Throwable?) : super(message, cause)
this.body(dto)
}
}
}
/**
* 错误参数
* 统一捕获并处理异常
*/
class IllegalParamException(message: String) : IllegalAccessException(message)
internal inline fun Route.intercept(crossinline blk: suspend PipelineContext<Unit, ApplicationCall>.() -> Unit) = handle {
try {
blk(this)
} catch (e: IllegalSessionException) {
call.respondStateCode(StateCode.IllegalSession)
} catch (e: NotVerifiedSessionException) {
call.respondStateCode(StateCode.NotVerifySession)
} catch (e: NoSuchElementException) {
call.respondStateCode(StateCode.NoElement)
} catch (e: IllegalAccessException) {
call.respondStateCode(StateCode(400, e.message), HttpStatusCode.BadRequest)
}
}
/*
extend function
*/
internal suspend inline fun <reified T : StateCode> ApplicationCall.respondStateCode(code: T, status: HttpStatusCode = HttpStatusCode.OK)
= respondJson(code.toJson(StateCode.serializer()), status)
internal suspend inline fun <reified T : DTO> ApplicationCall.respondDTO(dto: T, status: HttpStatusCode = HttpStatusCode.OK)
= respondJson(dto.toJson(), status)
internal suspend fun ApplicationCall.respondJson(json: String, status: HttpStatusCode = HttpStatusCode.OK) =
respondText(json, defaultTextContentType(ContentType("application", "json")), status)
internal suspend inline fun <reified T : DTO> ApplicationCall.receiveDTO(): T? = receive<String>().jsonParseOrNull()
fun PipelineContext<Unit, ApplicationCall>.illegalParam(
expectingType: String?,
......@@ -138,11 +140,7 @@ fun PipelineContext<Unit, ApplicationCall>.illegalParam(
@Suppress("IMPLICIT_CAST_TO_ANY")
@UseExperimental(ExperimentalUnsignedTypes::class)
private inline fun <reified R> PipelineContext<Unit, ApplicationCall>.param(name: String): R = this.paramOrNull(name) ?: illegalParam(R::class.simpleName, name)
@Suppress("IMPLICIT_CAST_TO_ANY")
@UseExperimental(ExperimentalUnsignedTypes::class)
private inline fun <reified R> PipelineContext<Unit, ApplicationCall>.paramOrNull(name: String): R? =
internal inline fun <reified R> PipelineContext<Unit, ApplicationCall>.paramOrNull(name: String): R =
when (R::class) {
Byte::class -> call.parameters[name]?.toByte()
Int::class -> call.parameters[name]?.toInt()
......@@ -165,7 +163,34 @@ private inline fun <reified R> PipelineContext<Unit, ApplicationCall>.paramOrNul
UInt::class -> call.parameters[name]?.toUInt()
UShort::class -> call.parameters[name]?.toUShort()
UByteArray::class -> call.parameters[name]?.hexToUBytes()
ByteArray::class -> call.parameters[name]?.hexToBytes()
else -> error(name::class.simpleName + " is not supported")
} as R?
} as R ?: illegalParam(R::class.simpleName, name)
/**
* 错误请求. 抛出这个异常后将会返回错误给一个请求
*/
@Suppress("unused")
open class IllegalAccessException : Exception {
override val message: String get() = super.message!!
constructor(message: String) : super(message, null)
constructor(cause: Throwable) : super(cause.toString(), cause)
constructor(message: String, cause: Throwable?) : super(message, cause)
}
/**
* Session失效或不存在
*/
object IllegalSessionException : IllegalAccessException("Session失效或不存在")
/**
* Session未激活
*/
object NotVerifiedSessionException : IllegalAccessException("Session未激活")
/**
* 错误参数
*/
class IllegalParamException(message: String) : IllegalAccessException(message)
package net.mamoe.mirai.api.http.route
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.routing.routing
import net.mamoe.mirai.api.http.dto.*
fun Application.messageModule() {
routing {
miraiGet("/fetchMessage") {
val count: Int = paramOrNull("count")
val fetch = it.messageQueue.fetch(count)
val ls = Array(fetch.size) { index -> fetch[index].toDTO() }
call.respondJson(ls.toList().toJson())
}
miraiVerify<SendDTO>("/sendFriendMessage") {
it.session.bot.getFriend(it.target).sendMessage(it.messageChain.toMessageChain())
call.respondStateCode(StateCode.Success)
}
miraiVerify<SendDTO>("/sendGroupMessage") {
it.session.bot.getGroup(it.target).sendMessage(it.messageChain.toMessageChain())
call.respondStateCode(StateCode.Success)
}
miraiVerify<VerifyDTO>("/event/message") {
}
miraiVerify<VerifyDTO>("/addFriend") {
}
}
}
\ No newline at end of file
......@@ -89,18 +89,18 @@ internal class MemberImpl(
} else if (myPermission == MemberPermission.MEMBER) {
return false
}
try {
return try {
bot.network.run {
val response = TroopManagement.Mute(
TroopManagement.Mute(
client = bot.client,
groupCode = group.id,
memberUin = this@MemberImpl.id,
timeInSecond = durationSeconds
).sendAndExpect<TroopManagement.Mute.Response>()
}
return true
true
} catch (e: Exception) {
return false
false
}
}
......
......@@ -90,8 +90,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
is LoginPacket.LoginPacketResponse.DeviceLockLogin -> {
response = LoginPacket.SubCommand20(
bot.client,
response.t402,
response.t403
response.t402
).sendAndExpect()
continue@mainloop
}
......
......@@ -28,8 +28,8 @@ internal data class RequestPushNotify(
@Suppress("ArrayInDataClass")
@Serializable
internal data class MsgInfo(
@SerialId(0) val lFromUin: Long = 0L,
@SerialId(1) val uMsgTime: Long = 0L,
@SerialId(0) val lFromUin: Long? = 0L,
@SerialId(1) val uMsgTime: Long? = 0L,
@SerialId(2) val shMsgType: Short,
@SerialId(3) val shMsgSeq: Short,
@SerialId(4) val strMsg: String?,
......
......@@ -361,7 +361,7 @@ internal object KnownPacketFactories {
}
@UseExperimental(ExperimentalContracts::class)
internal inline fun <I : IoBuffer, R> I.withUse(block: I.() -> R): R {
internal inline fun <R> IoBuffer.withUse(block: IoBuffer.() -> R): R {
contract {
callsInPlace(block, kotlin.contracts.InvocationKind.EXACTLY_ONCE)
}
......@@ -373,7 +373,7 @@ internal inline fun <I : IoBuffer, R> I.withUse(block: I.() -> R): R {
}
@UseExperimental(ExperimentalContracts::class)
internal inline fun <I : ByteReadPacket, R> I.withUse(block: I.() -> R): R {
internal inline fun <R> ByteReadPacket.withUse(block: ByteReadPacket.() -> R): R {
contract {
callsInPlace(block, kotlin.contracts.InvocationKind.EXACTLY_ONCE)
}
......
......@@ -146,8 +146,7 @@ fun BytePacketBuilder.t116(
fun BytePacketBuilder.t100(
appId: Long = 16,
subAppId: Long = 537062845,
appClientVersion: Int,
sigMap: Int
appClientVersion: Int
) {
writeShort(0x100)
writeShortLVPacket {
......@@ -156,7 +155,7 @@ fun BytePacketBuilder.t100(
writeInt(appId.toInt())
writeInt(subAppId.toInt())
writeInt(appClientVersion)
writeInt(34869472) // 34869472?
writeInt(34869472) // sigMap, 34869472?
} shouldEqualsTo 22
}
......
......@@ -72,8 +72,7 @@ internal object LoginPacket : OutgoingPacketFactory<LoginPacket.LoginPacketRespo
@UseExperimental(MiraiInternalAPI::class)
operator fun invoke(
client: QQAndroidClient,
t402: ByteArray,
t403: ByteArray
t402: ByteArray
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) {
......@@ -96,10 +95,7 @@ internal object LoginPacket : OutgoingPacketFactory<LoginPacket.LoginPacketRespo
private const val subAppId = 537062845L
@UseExperimental(MiraiInternalAPI::class)
operator fun invoke(
client: QQAndroidClient,
t174: ByteArray,
t402: ByteArray,
phoneNumber: String
client: QQAndroidClient
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId, unknownHex = "01 00 00 00 00 00 00 00 00 00 01 00") {
writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) {
......@@ -163,7 +159,7 @@ internal object LoginPacket : OutgoingPacketFactory<LoginPacket.LoginPacketRespo
if (ConfigManager.get_loginWithPicSt()) appIdList = longArrayOf(1600000226L)
*/
t116(client.miscBitMap, client.subSigMap)
t100(appId, subAppId, client.appClientVersion, client.mainSigMap or 0xC0)
t100(appId, subAppId, client.appClientVersion)
t107(0)
// t108(byteArrayOf())
......@@ -310,7 +306,7 @@ internal object LoginPacket : OutgoingPacketFactory<LoginPacket.LoginPacketRespo
@UseExperimental(MiraiDebugAPI::class)
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): LoginPacketResponse {
val subCommand = readUShort().toInt()
discardExact(2) // subCommand
// println("subCommand=$subCommand")
val type = readUByte()
// println("type=$type")
......@@ -703,7 +699,7 @@ internal object LoginPacket : OutgoingPacketFactory<LoginPacket.LoginPacketRespo
private fun QQAndroidClient.analysisTlv149(t149: ByteArray): LoginPacketResponse.Error {
return t149.read {
val type: Short = readShort()
discardExact(2) //type
val title: String = readUShortLVString()
val content: String = readUShortLVString()
val otherInfo: String = readUShortLVString()
......
package net.mamoe.mirai.qqandroid.network.protocol.packet.login
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.packet.*
internal object TransEmpPacket : OutgoingPacketFactory<TransEmpPacket.Response>("wtlogin.trans_emp") {
private const val appId = 16L
private const val subAppId = 537062845L
@Suppress("FunctionName")
fun SubCommand1(
client: QQAndroidClient
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) {
writeOicqRequestPacket(client, EncryptMethodECDH135(client.ecdh), TODO()) {
// oicq.wlogin_sdk.request.trans_emp_1#packTransEmpBody
}
}
object Response : Packet
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
TODO("not implemented")
}
}
\ No newline at end of file
......@@ -175,7 +175,7 @@ fun ByteReadPacket.analysisOneFullPacket(): ByteReadPacket = debugIfFail("Failed
readShort().toInt().takeIf { it != 8001 }?.let {
println("这个包不是 oicqRequest")
return@debugIfFail this
println(" got new protocolVersion=$it")
//println(" got new protocolVersion=$it")
}
val commandId = readUShort().toInt()
println(" commandId=0x${commandId.toShort().toUHexString()}")
......
......@@ -11,7 +11,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
*/
class At @MiraiInternalAPI constructor(val target: Long, val display: String) : Message {
@UseExperimental(MiraiInternalAPI::class)
constructor(member: Member) : this(member.id, member.groupCard)
constructor(member: Member) : this(member.id, "@${member.groupCard}")
override fun toString(): String = display
......
......@@ -76,7 +76,7 @@ suspend fun main() {
startsWith("profile", removePrefix = true) {
val account = it.trim()
if (account.isNotEmpty()) {
account.toLong().qq()
bot.getFriend(account.toLong())
} else {
sender
}.queryProfile().toString().reply()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment