Skip to content

Commit

Permalink
1. Support CQHTTP Quick Operation
Browse files Browse the repository at this point in the history
2. Add debug logging when handling cq operation
3. Typo in README.md
  • Loading branch information
yyuueexxiinngg committed May 24, 2020
1 parent 216f1d5 commit a8f11b2
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 59 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ __CQHTTP runs on Mirai__
reversePort: 8080
# 反向Websocket路径
reversePath: /ws
# 访问T口令, 默认为null, 即不设置Token
# 访问口令, 默认为null, 即不设置Token
accessToken: null
# 反向Websocket Api路径 尚未实现
# reverseApiPath: /ws/
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {
}

group = "yyuueexxiinngg"
version = "0.1.1"
version = "0.1.2"

repositories {
maven(url = "https://mirrors.huaweicloud.com/repository/maven")
Expand Down
69 changes: 64 additions & 5 deletions src/main/kotlin/tech/mihoyo/mirai/MiraiApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class MiraiApi(val bot: Bot) {
val cachedTempContact: MutableMap<Long, Long> = mutableMapOf()
private val cachedSourceQueue = CacheSourceQueue()

suspend fun cqSendMessage(params: JsonObject): CQResponseDTO {
suspend fun cqSendMessage(params: Map<String, JsonElement>): CQResponseDTO {
if (params.contains("message_type")) {
when (params["message_type"]?.content) {
"private" -> return cqSendPrivateMessage(params)
Expand All @@ -32,7 +32,7 @@ class MiraiApi(val bot: Bot) {
return CQResponseDTO.CQInvalidRequest
}

suspend fun cqSendGroupMessage(params: JsonObject): CQResponseDTO {
suspend fun cqSendGroupMessage(params: Map<String, JsonElement>): CQResponseDTO {
val targetGroupId = params["group_id"]!!.long
val raw = params["auto_escape"]?.booleanOrNull ?: false
val messages = try {
Expand All @@ -53,7 +53,7 @@ class MiraiApi(val bot: Bot) {
return CQResponseDTO.CQInvalidRequest
}

suspend fun cqSendPrivateMessage(params: JsonObject): CQResponseDTO {
suspend fun cqSendPrivateMessage(params: Map<String, JsonElement>): CQResponseDTO {
val targetQQId = params["user_id"]!!.long
val raw = params["auto_escape"]?.booleanOrNull ?: false
val messages = try {
Expand Down Expand Up @@ -166,7 +166,7 @@ class MiraiApi(val bot: Bot) {
}
}

suspend fun cqSetFriendAddRequest(params: JsonObject): CQResponseDTO {
suspend fun cqSetFriendAddRequest(params: Map<String, JsonElement>): CQResponseDTO {
val flag = params["flag"]?.contentOrNull
val approve = params["approve"]?.booleanOrNull ?: true
val remark = params["remark"]?.contentOrNull
Expand All @@ -186,7 +186,7 @@ class MiraiApi(val bot: Bot) {
}
}

suspend fun cqSetGroupAddRequest(params: JsonObject): CQResponseDTO {
suspend fun cqSetGroupAddRequest(params: Map<String, JsonElement>): CQResponseDTO {
val flag = params["flag"]?.contentOrNull
val type = params["type"]?.contentOrNull
val subType = params["sub_type"]?.contentOrNull
Expand Down Expand Up @@ -270,6 +270,65 @@ class MiraiApi(val bot: Bot) {
}
}

// https://github.com/richardchien/coolq-http-api/blob/master/src/cqhttp/plugins/web/http.cpp#L375
suspend fun cqHandleQuickOperation(params: JsonObject): CQResponseDTO {
try {
val context = params["context"]?.jsonObject
val operation = params["operation"]?.jsonObject
val postType = context?.get("post_type")?.content

if (postType == "message") {
val messageType = context["message_type"]?.content

var reply = operation?.get("reply")?.content
if (reply != null) {
if (messageType == "group" && operation?.get("at_sender")?.booleanOrNull == true) {
context["user_id"]?.longOrNull?.apply {
reply = "[CQ:at,qq=$this] $reply"
}
}
val nextCallParams = context.toMutableMap()
nextCallParams["message"] = JsonPrimitive(reply)
return cqSendMessage(nextCallParams)
}

if (messageType == "group") {
// TODO: 备忘, 暂未支持
val isAnonymous = false
if (operation?.get("delete")?.booleanOrNull == true) {
return cqDeleteMessage(context)
}
if (operation?.get("kick")?.booleanOrNull == true) {
return cqSetGroupKick(context)
}
if (operation?.get("ban")?.booleanOrNull == true) {
@Suppress("ConstantConditionIf")
(return if (isAnonymous) {
cqSetAnonymousBan(context)
} else {
cqSetGroupBan(context)
})
}
}
} else if (postType == "request") {
val requestType = context["request_type"]?.content
val approveOpt = operation?.get("approve")?.booleanOrNull ?: false
val nextCallParams = context.toMutableMap()
nextCallParams["approve"] = JsonPrimitive(approveOpt)
nextCallParams["remark"] = JsonPrimitive(operation?.get("remark")?.contentOrNull)
nextCallParams["reason"] = JsonPrimitive(operation?.get("reason")?.contentOrNull)
if (requestType == "friend") {
return cqSetFriendAddRequest(nextCallParams)
} else if (requestType == "group") {
return cqSetGroupAddRequest(nextCallParams)
}
}
return CQResponseDTO.CQInvalidRequest
} catch (e: Exception) {
return CQResponseDTO.CQPluginFailure
}
}

/**
* Getting image path, not supported for now
*/
Expand Down
3 changes: 3 additions & 0 deletions src/main/kotlin/tech/mihoyo/mirai/PluginBase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package tech.mihoyo.mirai

import kotlinx.coroutines.async
import net.mamoe.mirai.console.plugins.PluginBase
import net.mamoe.mirai.console.plugins.withDefault
import tech.mihoyo.mirai.web.HttpApiServices
import java.io.File

object PluginBase : PluginBase() {
private val config = loadConfig("setting.yml")
val debug by config.withDefault { false }
var services: HttpApiServices = HttpApiServices(this)

override fun onLoad() {
Expand Down
70 changes: 37 additions & 33 deletions src/main/kotlin/tech/mihoyo/mirai/util/CQMessgeParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,16 @@ suspend fun cqMessageToMessageChains(
cqMessage: Any?,
raw: Boolean = false
): MessageChain? {
if (cqMessage != null) {
var messageChain: MessageChain? = null
if (cqMessage is String) {
messageChain = if (raw) {
return when (cqMessage) {
is String -> {
return if (raw) {
PlainText(cqMessage).asMessageChain()
} else {
codeToChain(bot, cqMessage, contact)
}
} else if (cqMessage is JsonArray) {
messageChain = buildMessageChain { }
}
is JsonArray -> {
var messageChain = buildMessageChain { }
for (msg in cqMessage) {
try {
val data = msg.jsonObject["data"]
Expand All @@ -64,49 +64,53 @@ suspend fun cqMessageToMessageChains(
else -> messageChain += cqTextToMessageInternal(bot, contact, msg)
}
} catch (e: NullPointerException) {
bot.logger.warning("Got null when parsing CQ message array")
logger.warning("Got null when parsing CQ message array")
continue
}
}
} else if (cqMessage is JsonObject) {
try {
return messageChain
}
is JsonObject -> {
return try {
val data = cqMessage.jsonObject["data"]
messageChain = when (cqMessage.jsonObject["type"]?.content) {
when (cqMessage.jsonObject["type"]?.content) {
"text" -> PlainText(data!!.jsonObject["text"]!!.content).asMessageChain()
else -> cqTextToMessageInternal(bot, contact, cqMessage).asMessageChain()
}
} catch (e: NullPointerException) {
bot.logger.warning("Got null when parsing CQ message object")
logger.warning("Got null when parsing CQ message object")
null
}
}
return messageChain
else -> null
}
return null
}


private suspend fun cqTextToMessageInternal(bot: Bot, contact: Contact?, message: Any): Message {
if (message is String) {
if (message.startsWith("[CQ:") && message.endsWith("]")) {
val parts = message.substring(4, message.length - 1).split(delimiters = *arrayOf(","), limit = 2)
return when (message) {
is String -> {
if (message.startsWith("[CQ:") && message.endsWith("]")) {
val parts = message.substring(4, message.length - 1).split(delimiters = *arrayOf(","), limit = 2)

lateinit var args: HashMap<String, String>
args = if (parts.size == 2) {
parts[1].toMap()
} else {
HashMap()
lateinit var args: HashMap<String, String>
args = if (parts.size == 2) {
parts[1].toMap()
} else {
HashMap()
}
return convertToMiraiMessage(bot, contact, parts[0], args)
}

return convertToMiraiMessage(bot, contact, parts[0], args)
return PlainText(message.unescape())
}
return PlainText(message.unescape())
} else if (message is JsonObject) {
val type = message.jsonObject["type"]!!.content
val data = message.jsonObject["data"] ?: return MSG_EMPTY
val args = data.jsonObject.keys.map { it to data.jsonObject[it]!!.content }.toMap()
return convertToMiraiMessage(bot, contact, type, args)
is JsonObject -> {
val type = message.jsonObject["type"]!!.content
val data = message.jsonObject["data"] ?: return MSG_EMPTY
val args = data.jsonObject.keys.map { it to data.jsonObject[it]!!.content }.toMap()
return convertToMiraiMessage(bot, contact, type, args)
}
else -> MSG_EMPTY
}
return MSG_EMPTY
}

private suspend fun convertToMiraiMessage(
Expand Down Expand Up @@ -206,7 +210,7 @@ private suspend fun convertToMiraiMessage(
return PokeMessage.Poke
}
else -> {
bot.logger.debug("不支持的 CQ码:${type}")
logger.debug("不支持的 CQ码:${type}")
}
}
return MSG_EMPTY
Expand Down Expand Up @@ -249,7 +253,7 @@ suspend fun codeToChain(bot: Bot, message: String, contact: Contact?): MessageCh
message.forEach { c: Char ->
if (c == '[') {
if (interpreting) {
bot.logger.error("CQ消息解析失败:$message,索引:$index")
logger.error("CQ消息解析失败:$message,索引:$index")
return@forEach
} else {
interpreting = true
Expand All @@ -262,7 +266,7 @@ suspend fun codeToChain(bot: Bot, message: String, contact: Contact?): MessageCh
}
} else if (c == ']') {
if (!interpreting) {
bot.logger.error("CQ消息解析失败:$message,索引:$index")
logger.error("CQ消息解析失败:$message,索引:$index")
return@forEach
} else {
interpreting = false
Expand Down
49 changes: 49 additions & 0 deletions src/main/kotlin/tech/mihoyo/mirai/util/Logger.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package tech.mihoyo.mirai.util

import net.mamoe.mirai.utils.MiraiLoggerPlatformBase
import tech.mihoyo.mirai.PluginBase

class Logger(override val identity: String?) : MiraiLoggerPlatformBase() {
private val consoleLogger = PluginBase.logger
override fun verbose0(message: String?) {
consoleLogger.verbose(message)
}

override fun verbose0(message: String?, e: Throwable?) {
consoleLogger.verbose(message, e)
}

override fun debug0(message: String?) {
if (PluginBase.debug) consoleLogger.debug(message)
}

override fun debug0(message: String?, e: Throwable?) {
if (PluginBase.debug) consoleLogger.debug(message, e)
}

override fun info0(message: String?) {
consoleLogger.info(message)
}

override fun info0(message: String?, e: Throwable?) {
consoleLogger.info(message, e)
}

override fun warning0(message: String?) {
consoleLogger.warning(message)
}

override fun warning0(message: String?, e: Throwable?) {
consoleLogger.warning(message, e)
}

override fun error0(message: String?) {
consoleLogger.error(message)
}

override fun error0(message: String?, e: Throwable?) {
consoleLogger.error(message, e)
}
}

val logger = Logger("CQHTTPAPI")
2 changes: 0 additions & 2 deletions src/main/kotlin/tech/mihoyo/mirai/web/http/ReportService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ class ReportService(
append("User-Agent", "MiraiHttp/0.1.0")
append("X-Self-ID", botId.toString())
secret.takeIf { it != "" }?.apply {
println("Triggered")
println(this)
append("X-Signature", getSha1Hash(botId, json))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.*
import net.mamoe.mirai.contact.PermissionDeniedException
import tech.mihoyo.mirai.MiraiApi
import tech.mihoyo.mirai.PluginBase
import tech.mihoyo.mirai.data.common.CQResponseDTO
import tech.mihoyo.mirai.util.logger
import tech.mihoyo.mirai.util.toJson

@OptIn(UnstableDefault::class)
suspend fun handleWebSocketActions(outgoing: SendChannel<Frame>, mirai: MiraiApi, cqActionText: String) {
try {
logger.debug(cqActionText)
val json = Json.parseJson(cqActionText).jsonObject
val echo = json["echo"]
lateinit var responseDTO: CQResponseDTO
// Same exceptions are not catchable ...?
var responseDTO: CQResponseDTO = CQResponseDTO.CQPluginFailure
try {
when (json["action"]?.content) {
"send_msg" -> responseDTO = mirai.cqSendMessage(json["params"]!!.jsonObject)
Expand Down Expand Up @@ -55,17 +57,23 @@ suspend fun handleWebSocketActions(outgoing: SendChannel<Frame>, mirai: MiraiApi
"set_restart_plugin" -> responseDTO = mirai.cqSetRestartPlugin(json["params"]!!.jsonObject)
"clean_data_dir" -> responseDTO = mirai.cqCleanDataDir(json["params"]!!.jsonObject)
"clean_plugin_log" -> responseDTO = mirai.cqCleanPluginLog(json["params"]!!.jsonObject)
else -> println(json["action"]?.content)
".handle_quick_operation_async" -> responseDTO = mirai.cqHandleQuickOperation(json["params"]!!.jsonObject)
".handle_quick_operation" -> responseDTO = mirai.cqHandleQuickOperation(json["params"]!!.jsonObject)
else -> {
logger.error("未知CQHTTP API: ${json["action"]?.content}")
}
}
} catch (e: PermissionDeniedException) {
responseDTO = CQResponseDTO.CQMiraiFailure
} catch (e: Exception) {
PluginBase.logger.error(e)
logger.error(e)
responseDTO = CQResponseDTO.CQPluginFailure
}
responseDTO.echo = echo
outgoing.send(Frame.Text(responseDTO.toJson()))
val jsonToSend = responseDTO.toJson()
logger.debug(jsonToSend)
outgoing.send(Frame.Text(jsonToSend))
} catch (e: Exception) {
PluginBase.logger.error(e)
logger.error(e)
}
}
Loading

0 comments on commit a8f11b2

Please sign in to comment.