diff --git a/mirai-api-http/README_CH.md b/mirai-api-http/README_CH.md
index a56c1c5a7..45b54c36c 100644
--- a/mirai-api-http/README_CH.md
+++ b/mirai-api-http/README_CH.md
@@ -3,12 +3,11 @@
Mirai-API-http 提供HTTP API供所有语言使用mirai
-
+
### 开始会话-认证(Authorize)
-```php
-路径: /auth
-方法: POST
+```
+[POST] /auth
```
使用此方法验证你的会话连接, 并将这个会话绑定一个BOT
注意: 每个会话只能绑定一个BOT.
@@ -20,36 +19,34 @@ Mirai-API-http 提供HTTP API供所有语言使用mirai
| key | String |false|U9HSaDXl39ksd918273hU|MIRAI API HTTP key, HTTP API的核心key|
| qq | String |false|1040400290|需要绑定的BOT QQ号|
-
+
#### 返回(成功):
| 名字 | 类型 | 举例 | 说明|
| --- | --- | --- | --- |
-| success |Boolean |true|是否验证成功|
+| code |Int |0|返回状态|
| session |String |UANSHDKSLAOISN|你的session key|
-
-#### 返回(失败):
-
-| name | type | example|note|
-| --- | --- | --- | --- |
-| success |Boolean |false|是否验证成功|
-| session |String |null|你的session key|
-| error |int |0|错误码|
-
-#### 错误码:
+#### 状态码:
| 代码 | 原因|
| --- | --- |
-| 0 | 错误的MIRAI API HTTP key |
-| 1 | 试图绑定不存在的bot|
-
+| 0 | 正常 |
+| 1 | 错误的MIRAI API HTTP key|
+| 2 | 试图绑定不存在的bot|
session key 是使用以下方法必须携带的
session key 需要被以cookie的形式上报 cookies :
-
- | 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
+```
+
diff --git a/mirai-api-http/build.gradle.kts b/mirai-api-http/build.gradle.kts
index 898ad8455..dc472dfcf 100644
--- a/mirai-api-http/build.gradle.kts
+++ b/mirai-api-http/build.gradle.kts
@@ -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")
}
}
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/MiraiHttpAPIServer.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/MiraiHttpAPIServer.kt
new file mode 100644
index 000000000..2d4664b2f
--- /dev/null
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/MiraiHttpAPIServer.kt
@@ -0,0 +1,42 @@
+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
diff --git a/mirai-api-http/src/main/kotlin/net.mamoe.mirai.api.http/Session.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/Session.kt
similarity index 73%
rename from mirai-api-http/src/main/kotlin/net.mamoe.mirai.api.http/Session.kt
rename to mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/Session.kt
index 1a03a01d1..c1c583291 100644
--- a/mirai-api-http/src/main/kotlin/net.mamoe.mirai.api.http/Session.kt
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/Session.kt
@@ -1,9 +1,11 @@
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()
}
}
@@ -89,11 +95,20 @@ class TempSession internal constructor(coroutineContext: CoroutineContext) : Ses
* 任何[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>
+ init {
+ bot.subscribeMessages {
+ _listener = always { this.run(messageQueue::add) }
+ }
+ }
+
+ override fun close() {
+ _listener.complete()
+ super.close()
+ }
}
-
-
-
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/dto/AuthDTO.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/dto/AuthDTO.kt
new file mode 100644
index 000000000..150d34c4f
--- /dev/null
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/dto/AuthDTO.kt
@@ -0,0 +1,9 @@
+package net.mamoe.mirai.api.http.dto
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class AuthDTO(val authKey: String) : DTO
+
+@Serializable
+data class AuthResDTO(val code: Int, val session: String) : DTO
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/dto/ContactDTO.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/dto/ContactDTO.kt
new file mode 100644
index 000000000..c3d254785
--- /dev/null
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/dto/ContactDTO.kt
@@ -0,0 +1,39 @@
+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
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/dto/DTO.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/dto/DTO.kt
new file mode 100644
index 000000000..6ad39c5f1
--- /dev/null
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/dto/DTO.kt
@@ -0,0 +1,42 @@
+package net.mamoe.mirai.api.http.dto
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.modules.SerializersModule
+
+interface DTO
+
+object MiraiJson {
+ val json = Json(context = SerializersModule {
+ polymorphic(MessagePacketDTO.serializer()) {
+ GroupMessagePacketDTO::class with GroupMessagePacketDTO.serializer()
+ FriendMessagePacketDTO::class with FriendMessagePacketDTO.serializer()
+ }
+ })
+}
+
+// 解析失败时直接返回null,由路由判断响应400状态
+@UseExperimental(ImplicitReflectionSerializer::class)
+inline fun String.jsonParseOrNull(
+ serializer: DeserializationStrategy? = null
+): T? = try {
+ if(serializer == null) MiraiJson.json.parse(this) else MiraiJson.json.parse(serializer, this)
+} catch (e: Exception) { throw e }
+
+
+
+@UseExperimental(ImplicitReflectionSerializer::class, UnstableDefault::class)
+inline fun T.toJson(
+ serializer: SerializationStrategy? = null
+): String = if (serializer == null) MiraiJson.json.stringify(this)
+ else Json.stringify(serializer, this)
+
+
+
+// 序列化列表时,stringify需要使用的泛型是T,而非List
+// 因为使用的stringify的stringify(objs: List)重载
+@UseExperimental(ImplicitReflectionSerializer::class, UnstableDefault::class)
+inline fun List.toJson(
+ serializer: SerializationStrategy>? = null
+): String = if (serializer == null) MiraiJson.json.stringify(this)
+else Json.stringify(serializer, this)
\ No newline at end of file
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/dto/MessageDTO.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/dto/MessageDTO.kt
new file mode 100644
index 000000000..75454633f
--- /dev/null
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/dto/MessageDTO.kt
@@ -0,0 +1,86 @@
+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.*
+
+/*
+ DTO data class
+ */
+
+@Serializable
+@SerialName("FriendMessage")
+data class FriendMessagePacketDTO(val sender: QQDTO) : MessagePacketDTO()
+
+@Serializable
+@SerialName("GroupMessage")
+data class GroupMessagePacketDTO(val sender: MemberDTO) : MessagePacketDTO()
+
+@Serializable
+data class MessageDTO(val type: MessageType, val data: String) : DTO
+
+typealias MessageChainDTO = Array
+
+@Serializable
+abstract class MessagePacketDTO : DTO {
+ lateinit var messageChain : MessageChainDTO
+
+ companion object {
+ val EMPTY = @SerialName("UnknownMessage") object : MessagePacketDTO() {}
+ }
+}
+
+
+/*
+ Extend function
+ */
+suspend fun MessagePacket<*, *>.toDTO(): MessagePacketDTO = when (this) {
+ is FriendMessage -> FriendMessagePacketDTO(QQDTO(sender))
+ is GroupMessage -> GroupMessagePacketDTO(MemberDTO(sender, senderName))
+ else -> MessagePacketDTO.EMPTY
+}.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 -> MessageDTO(MessageType.AT, target.toString())
+ is Face -> MessageDTO(MessageType.FACE, id.value.toString())
+ is PlainText -> MessageDTO(MessageType.PLAIN, stringValue)
+// is Image -> MessageDTO(MessageType.IMAGE, ???)
+ is Image -> MessageDTO(MessageType.IMAGE, "NOT SUPPORT IMAGE NOW")
+ is XMLMessage -> MessageDTO(MessageType.XML, stringValue)
+ else -> MessageDTO(MessageType.UNKNOWN, "not support type")
+}
+
+@UseExperimental(ExperimentalUnsignedTypes::class)
+fun MessageDTO.toMessage() = when (type) {
+ MessageType.AT -> At(data.toLong())
+ MessageType.FACE -> Face(FaceId(data.toUByte()))
+ MessageType.PLAIN -> PlainText(data)
+// MessageType.IMAGE -> Image(???)
+ MessageType.IMAGE -> PlainText(data)
+ MessageType.XML -> XMLMessage(data)
+ MessageType.UNKNOWN -> PlainText(data)
+}
+
+
+/*
+ Enum
+ */
+
+// TODO: will be replace by [net.mamoe.mirai.message.MessageType]
+enum class MessageType {
+ AT,
+ FACE,
+ PLAIN,
+ IMAGE,
+ XML,
+ UNKNOWN,
+}
+
+
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/dto/VerifyDTO.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/dto/VerifyDTO.kt
new file mode 100644
index 000000000..01b3d1041
--- /dev/null
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/dto/VerifyDTO.kt
@@ -0,0 +1,35 @@
+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()
+
+
+// 写成data class并继承DTO接口是为了返回时的形式统一
+@Serializable
+open class StateCodeDTO(val code: Int, val msg: String) : DTO {
+ companion object {
+ val SUCCESS = StateCodeDTO(0, "success") // 成功
+// val AUTH_WRONG = CodeDTO(1) // AuthKey错误, @see AuthResDTO
+ val NO_BOT = StateCodeDTO(2, "指定Bot不存在")
+ val ILLEGAL_SESSION = StateCodeDTO(3, "Session失效或不存在")
+ val NOT_VERIFIED_SESSION = StateCodeDTO(3, "Session未认证")
+ }
+ class ILLEGAL_ACCESS(msg: String) : StateCodeDTO(400, msg) // 非法访问
+}
+
+@Serializable
+data class SendDTO(
+ override val sessionKey: String,
+ val target: Long,
+ val messageChain: MessageChainDTO
+) : VerifyDTO()
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt
new file mode 100644
index 000000000..f8475578d
--- /dev/null
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt
@@ -0,0 +1,17 @@
+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>() {
+
+ fun fetch(size: Int): List> {
+ var count = size
+ val ret = ArrayList>(count)
+ while (!this.isEmpty() && count-- > 0) {
+ ret.add(this.pop())
+ }
+ return ret
+ }
+}
\ No newline at end of file
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/AuthRouteModule.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/AuthRouteModule.kt
new file mode 100644
index 000000000..72a928281
--- /dev/null
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/AuthRouteModule.kt
@@ -0,0 +1,42 @@
+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.respondDTO(AuthResDTO(1, ""))
+ } else {
+ call.respondDTO(AuthResDTO(0, SessionManager.createTempSession().key))
+ }
+ }
+
+ miraiVerify("/verify", verifiedSessionKey = false) {
+ try {
+ val bot = Bot.instanceWhose(it.qq)
+ with(SessionManager) {
+ closeSession(it.sessionKey)
+ allSession[it.sessionKey] = AuthedSession(bot, EmptyCoroutineContext)
+ }
+ call.respondDTO(StateCodeDTO.SUCCESS)
+ } catch (e: NoSuchElementException) {
+ call.respondDTO(StateCodeDTO.NO_BOT)
+ }
+ }
+
+ miraiVerify("/release") {
+ SessionManager.closeSession(it.sessionKey)
+ call.respondDTO(StateCodeDTO.SUCCESS)
+ }
+
+ }
+}
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/BaseRoute.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/BaseRoute.kt
new file mode 100644
index 000000000..f0ffb97b1
--- /dev/null
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/BaseRoute.kt
@@ -0,0 +1,191 @@
+package net.mamoe.mirai.api.http.route
+
+import io.ktor.application.Application
+import io.ktor.application.ApplicationCall
+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.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.util.pipeline.ContextDsl
+import io.ktor.util.pipeline.PipelineContext
+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.*
+
+fun Application.mirai() {
+ install(DefaultHeaders)
+ install(CallLogging)
+
+ authModule()
+ messageModule()
+}
+
+/**
+ * Auth,处理http server的验证
+ * 为闭包传入一个AuthDTO对象
+ */
+@ContextDsl
+internal fun Route.miraiAuth(
+ path: String,
+ body: suspend PipelineContext.(AuthDTO) -> Unit
+): Route {
+ return route(path, HttpMethod.Post) {
+ intercept {
+ val dto = context.receiveDTO() ?: throw IllegalParamException("参数格式错误")
+ this.body(dto)
+ }
+ }
+}
+
+/**
+ * Get,用于获取bot的属性
+ * 验证请求参数中sessionKey参数的有效性
+ */
+@ContextDsl
+internal fun Route.miraiGet(
+ path: String,
+ body: suspend PipelineContext.(AuthedSession) -> Unit
+): Route {
+ return route(path, HttpMethod.Get) {
+ 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)
+ }
+ }
+ }
+}
+
+/**
+ * Verify,用于处理bot的行为请求
+ * 验证数据传输对象(DTO)中是否包含sessionKey字段
+ * 且验证sessionKey的有效性
+ *
+ * @param verifiedSessionKey 是否验证sessionKey是否被激活
+ *
+ * it 为json解析出的DTO对象
+ */
+@ContextDsl
+internal inline fun Route.miraiVerify(
+ path: String,
+ verifiedSessionKey: Boolean = true,
+ crossinline body: suspend PipelineContext.(T) -> Unit
+): Route {
+ return route(path, HttpMethod.Post) {
+ intercept {
+ val dto = context.receiveDTO() ?: throw IllegalParamException("参数格式错误")
+ SessionManager[dto.sessionKey]?.let {
+ when {
+ it is TempSession && verifiedSessionKey -> throw NotVerifiedSessionException
+ it is AuthedSession -> dto.session = it
+ }
+ } ?: throw IllegalSessionException
+
+ this.body(dto)
+ }
+ }
+}
+
+/**
+ * 统一捕获并处理异常
+ */
+internal inline fun Route.intercept(crossinline blk: suspend PipelineContext.() -> Unit) = handle {
+ try {
+ blk(this)
+ } catch (e: IllegalSessionException) {
+ call.respondDTO(StateCodeDTO.ILLEGAL_SESSION)
+ } catch (e: NotVerifiedSessionException) {
+ call.respondDTO(StateCodeDTO.NOT_VERIFIED_SESSION)
+ } catch (e: IllegalAccessException) {
+ call.respondDTO(StateCodeDTO.ILLEGAL_ACCESS(e.message), HttpStatusCode.BadRequest)
+ }
+}
+
+/*
+ extend function
+ */
+internal suspend inline fun 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 ApplicationCall.receiveDTO(): T? = receive().apply(::println).jsonParseOrNull()
+
+
+
+
+fun PipelineContext.illegalParam(
+ expectingType: String?,
+ paramName: String,
+ actualValue: String? = call.parameters[paramName]
+): Nothing = throw IllegalParamException("Illegal param. A $expectingType is required for `$paramName` while `$actualValue` is given")
+
+@Suppress("IMPLICIT_CAST_TO_ANY")
+@UseExperimental(ExperimentalUnsignedTypes::class)
+internal inline fun PipelineContext.paramOrNull(name: String): R =
+ when (R::class) {
+ Byte::class -> call.parameters[name]?.toByte()
+ Int::class -> call.parameters[name]?.toInt()
+ Short::class -> call.parameters[name]?.toShort()
+ Float::class -> call.parameters[name]?.toFloat()
+ Long::class -> call.parameters[name]?.toLong()
+ Double::class -> call.parameters[name]?.toDouble()
+ Boolean::class -> when (call.parameters[name]) {
+ "true" -> true
+ "false" -> false
+ "0" -> false
+ "1" -> true
+ null -> null
+ else -> illegalParam("boolean", name)
+ }
+
+ String::class -> call.parameters[name]
+
+ UByte::class -> call.parameters[name]?.toUByte()
+ UInt::class -> call.parameters[name]?.toUInt()
+ UShort::class -> call.parameters[name]?.toUShort()
+
+ else -> error(name::class.simpleName + " is not supported")
+ } 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)
+
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/EventRouteModule.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/EventRouteModule.kt
new file mode 100644
index 000000000..679581823
--- /dev/null
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/EventRouteModule.kt
@@ -0,0 +1,2 @@
+package net.mamoe.mirai.api.http.route
+
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt
new file mode 100644
index 000000000..af1db39f9
--- /dev/null
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt
@@ -0,0 +1,39 @@
+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.AuthedSession
+import net.mamoe.mirai.api.http.SessionManager
+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("/sendFriendMessage") {
+ it.session.bot.getQQ(it.target).sendMessage(it.messageChain.toMessageChain())
+ call.respondDTO(StateCodeDTO.SUCCESS)
+ }
+
+ miraiVerify("/sendGroupMessage") {
+ it.session.bot.getGroup(it.target).sendMessage(it.messageChain.toMessageChain())
+ call.respondDTO(StateCodeDTO.SUCCESS)
+ }
+
+ miraiVerify("/event/message") {
+
+ }
+
+ miraiVerify("/addFriend") {
+
+ }
+ }
+}
\ No newline at end of file