diff --git a/src/Java/Maaj/Maa-HTTP-Server-startup.bat b/src/Java/Maaj/Maa-HTTP-Server-startup.bat new file mode 100644 index 0000000000..4d73f416e4 --- /dev/null +++ b/src/Java/Maaj/Maa-HTTP-Server-startup.bat @@ -0,0 +1,18 @@ +@echo off +cd /d %~dp0 +set javaPath=%cd% +cd .. +::显示上级目录路径 + +::设置要临时加入到path环境变量中的路径 +set My_PATH=%cd% + +set PATH=%PATH%;%My_PATH% + +cd javaPath + +::下面写你其它脚本命令 + +java -jar .\Maa-HTTP-0.0.1.jar -port=8848 + +pause diff --git a/src/Java/Maaj/Readme.md b/src/Java/Maaj/Readme.md new file mode 100644 index 0000000000..2440f9b007 --- /dev/null +++ b/src/Java/Maaj/Readme.md @@ -0,0 +1,562 @@ +# Maa-HTTP接口实现 + +本项目为通过JNA(Java-Native-Access)调用Maa核心实现的HTTP+WebSocket接口实现. + +## 接口实现情况 + +| 功能名称 | 接口路径(命令) | HTTP实现 | WebSocket实现 | +|:-------|:--------------|:-------|:------------| +| 获取版本号 | getVersion | Yes | Yes | +| 创建连接 | connect | Yes | Yes | +| 添加任务 | appendTask | Yes | Yes | +| 设定任务参数 | setTaskParams | Yes | Yes | +| 开始执行 | start | Yes | Yes | +| 停止执行 | stop | Yes | Yes | +| 销毁实例 | destroy | Yes | Yes | +| 列出实例连接 | listInstance | Yes | Yes | +| 消息回调通知 | callBack | No | Yes | + +## 环境依赖 + +1. JDK1.8 + +2. Maa v3.9.0-alpha + + +## 如何编译 + +.\gradlew.bat jar + +## 使用方法 + +1. 把他丢进Maa文件夹下,形成如下文件结构. + +``` +MeoAssistantArknights_v3.9.0-beta.8 +│ MeoAsstGui.exe +│ MeoAssistant.dll +│ ... +└───Java-HTTP + │ Maa-HTTP-0.0.1.jar + │ Maa-HTTP-Server-startup.bat +``` + +2. 双击Maa-HTTP-Server-startup.bat启动. +3. 默认端口号8848钛金端口,如需修改,请修改 Maa-HTTP-Server-startup.bat 中的-port=8848 . +4. 当你看到如下内容则启动成功. + +``` +2022-05-31 22:20:03.000 [main] INFO Application - Autoreload is disabled because the development mode is off. +2022-05-31 22:20:03.407 [main] INFO Application - Application started in 0.458 seconds. +2022-05-31 22:20:03.407 [main] INFO Application - Application started: io.ktor.server.application.Application@fac80 +2022-05-31 22:20:03.717 [DefaultDispatcher-worker-1] INFO Application - Responding at http://0.0.0.0:8848 +``` + +## 接口文档 + +### HTTP部分 + +提供了如下功能 + +| 功能名称 | 接口路径 | +|:-------|:--------------| +| 获取版本号 | getVersion | +| 创建连接 | connect | +| 添加任务 | appendTask | +| 设定任务参数 | setTaskParams | +| 开始执行 | start | +| 停止执行 | stop | +| 销毁实例 | destroy | +| 列出连接 | listInstance | + +--- + +#### 接口名称 获取版本号 + +###### 1) 请求地址 + +> http://127.0.0.1:8848/API/V1/getVersion + +###### 2) 调用方式:HTTP get + +###### 3) 请求参数: 无 + +###### 4) 返回结果示例: + +```json +{ + "data": { + "version": "v3.9.0-beta" + }, + "code": 0, + "message": "success" +} +``` + +--- + +##### 接口名称 创建连接 + +###### 1) 请求地址 + +> http://127.0.0.1:8848/API/V1/connect + +###### 2) 调用方式:HTTP post + +###### 3) 请求参数: + +```json +{ + "adbPath": "C:\\MeoAssistantArknights3\\platform-tools\\adb.exe", + "host": "127.0.0.1:62001", + "detailJson": "" +} +``` + +###### Post参数: + +| 字段名称 | 字段说明 | 类型 | 必填 | 备注 | +| ---- |-----------------|--------|-----|-----| +| adbPath | ADB地址 | String | Y | - | +| host | 实例地址 | String | Y | - | +| detailJson | 有什么用我也不知道,反正就是有 | String | N | - | + +###### 4) 请求返回结果: + +```json +{ + "data": { + "id": "46b9d5cd60382f100b336b17d6817f8eda255f73", + "result": true + }, + "code": 0, + "message": "success" +} +``` + +###### 5) 请求返回结果参数说明: + +| 字段名称 | 字段说明 | 类型 | 备注 | +|------|-------------|---------|---- | +| id | 给连接实例创建的ID | String | - | +| result | 是否成功 | Boolean | - | + +--- + + +--- + +##### 接口名称 c + +###### 1) 请求地址 + +> http://127.0.0.1:8848/API/V1/appendTask + +###### 2) 调用方式:HTTP post + +###### 3) 请求参数: + +```json +{ + "id": "46b9d5cd60382f100b336b17d6817f8eda255f73", + "type": "Fight", + "params": { + "stage": "LastBattle" + } +} +``` + +###### Post参数: + +| 字段名称 | 字段说明 | 类型 | 必填 | 备注 | +| ---- |-------------------------------------------------------------------------------------------------------------------------------|--------|---- |-----| +| id | 创建连接返回的实例ID | String | Y | - | +| type | 任务类型,此次示例为刷理智战斗,详情参考[集成文档](https://raw.githubusercontent.com/MaaAssistantArknights/MaaAssistantArknights/master/docs/集成文档.md) | String | Y | - | +| params | 任务参数 | Json | Y | - | + +###### 4) 请求返回结果: + +```json +{ + "data": { + "id": "46b9d5cd60382f100b336b17d6817f8eda255f73", + "taskId": 0 + }, + "code": 0, + "message": "success" +} +``` + +###### 5) 请求返回结果参数说明: + +| 字段名称 | 字段说明 | 类型 | 备注 | +|------|------------|--------|---- | +| id | 实例ID | String | - | +| taskId | 任务ID,为0则失败 | Int | - | + +--- + +##### 接口名称 设定任务参数 + +###### 1) 请求地址 + +> http://127.0.0.1:8848/API/V1/setTaskParams + +###### 2) 调用方式:HTTP post + +###### 3) 请求参数: + +```json +{ + "id": "46b9d5cd60382f100b336b17d6817f8eda255f73", + "type": "Fight", + "taskId": 0, + "params": { + "stage": "LastBattle" + } +} +``` + +###### Post参数: + +| 字段名称 | 字段说明 | 类型 | 必填 | 备注 | +| ---- |-------------------------------------------------------------------------------------------------------------------------------|--------|---- |-----| +| id | 创建连接返回的实例ID | String | Y | - | +| taskId | 任务ID | Int | Y | - | +| type | 任务类型,此次示例为刷理智战斗,详情参考[集成文档](https://raw.githubusercontent.com/MaaAssistantArknights/MaaAssistantArknights/master/docs/集成文档.md) | String | Y | - | +| params | 任务参数 | Json | Y | - | + +###### 4) 请求返回结果: + +```json +{ + "data": { + "id": "46b9d5cd60382f100b336b17d6817f8eda255f73", + "result": false + }, + "code": 0, + "message": "success" +} +``` + +###### 5) 请求返回结果参数说明: + +| 字段名称 | 字段说明 | 类型 | 备注 | +|------|-------------|---------|---- | +| id | 给连接实例创建的ID | String | - | +| result | 是否成功 | Boolean | - | + +--- + +##### 接口名称 开始执行 + +###### 1) 请求地址 + +> http://127.0.0.1:8848/API/V1/start + +###### 2) 调用方式:HTTP post + +###### 3) 请求参数: + +```json +{ + "id": "ccd76e0c367511158ca774ff951a22e8bb62f5d3" +} +``` + +###### Post参数: + +| 字段名称 | 字段说明 | 类型 | 必填 | 备注 | +| ---- |--------|--------|---- |-----| +| id | 创建连接返回的实例ID | String | Y | - | + +###### 4) 请求返回结果: + +```json +{ + "data": { + "id": "ccd76e0c367511158ca774ff951a22e8bb62f5d3", + "result": true + }, + "code": 0, + "message": "success" +} +``` + +###### 5) 请求返回结果参数说明: + +| 字段名称 | 字段说明 | 类型 | 备注 | +|------|-------------|---------|---- | +| id | 给连接实例创建的ID | String | - | +| result | 是否成功 | Boolean | - | + +--- + +##### 接口名称 停止执行,清空任务链 + +###### 1) 请求地址 + +> http://127.0.0.1:8848/API/V1/stop + +###### 2) 调用方式:HTTP post + +###### 3) 请求参数: + +```json +{ + "id": "ccd76e0c367511158ca774ff951a22e8bb62f5d3" +} +``` + +###### Post参数: + +| 字段名称 | 字段说明 | 类型 | 必填 | 备注 | +| ---- |--------|--------|---- |-----| +| id | 创建连接返回的实例ID | String | Y | - | + +###### 4) 请求返回结果: + +```json +{ + "data": { + "id": "ccd76e0c367511158ca774ff951a22e8bb62f5d3", + "result": true + }, + "code": 0, + "message": "success" +} +``` + +###### 5) 请求返回结果参数说明: + +| 字段名称 | 字段说明 | 类型 | 备注 | +|------|-------------|---------|---- | +| id | 给连接实例创建的ID | String | - | +| result | 是否成功 | Boolean | - | + +--- + +##### 接口名称 销毁实例 + +###### 1) 请求地址 + +> http://127.0.0.1:8848/API/V1/destroy + +###### 2) 调用方式:HTTP post + +###### 3) 请求参数: + +```json +{ + "id": "ccd76e0c367511158ca774ff951a22e8bb62f5d3" +} +``` + +###### Post参数: + +| 字段名称 | 字段说明 | 类型 | 必填 | 备注 | +| ---- |--------|--------|---- |-----| +| id | 创建连接返回的实例ID | String | Y | - | + +###### 4) 请求返回结果: + +```json +{ + "data": { + "id": "ccd76e0c367511158ca774ff951a22e8bb62f5d3", + "result": true + }, + "code": 0, + "message": "success" +} +``` + +###### 5) 请求返回结果参数说明: + +| 字段名称 | 字段说明 | 类型 | 备注 | +|------|-------------|---------|---- | +| id | 给连接实例创建的ID | String | - | +| result | 是否成功 | Boolean | - | + +--- + +##### 接口名称 列出实例连接 + +###### 1) 请求地址 + +> http://127.0.0.1:8848/API/V1/listInstance + +###### 2) 调用方式:HTTP get + +###### 3) 请求参数: 无 + +###### 4) 请求返回结果: + +```json +{ + "data": { + "list": [ + { + "id": "ccd76e0c367511158ca774ff951a22e8bb62f5d3", + "host": "127.0.0.1:62026", + "adbPath": "C:\\Users\\atmzx\\Desktop\\MeoAssistantArknights3\\platform-tools\\adb.exe", + "uuid": "", + "status": 0 + } + ] + }, + "code": 0, + "message": "success" +} +``` + +###### 5) 请求返回结果参数说明: + +| 字段名称 | 字段说明 | 类型 | 备注 | +|------|-------------|--------|---- | +| id | 实例ID | String | - | +| host | 连接的Host | String | - | +| adbPath | 使用的Adb地址 | String | - | +| uuid | 设备备ID,保留 | String | - | +| status | 0待机中,1任务执行中 | Int | - | + +### WebSocket部分 + +请求地址(没错跟HTTP同样是8848钛金端口 + +> ws://127.0.0.1:8848/API/V1 + +提供了如下功能 + +| 功能名称 | 命令 | +|:-------|:--------------| +| 获取版本号 | getVersion | +| 创建连接 | connect | +| 添加任务 | appendTask | +| 设定任务参数 | setTaskParams | +| 开始执行 | start | +| 停止执行 | stop | +| 销毁实例 | destroy | +| 列出实例连接 | listInstance | +| 消息回调通知 | callBack | + +所有的的WebSocket接口跟HTTP接口请求响应参数均完全一致 + +--- + +##### 接口名称 获取版本号 + +```json +{ + "command": "getVersion", + "msgId": 114514, + "data": {} +} +``` + +| 字段名称 | 字段说明 | 类型 | 必填 | 备注 | +| ---- |-----------------------------------|--------|---- |-----| +| command | 操作命令 | String |,Y | - | +| msgId | 消息ID,服务端收到此次请求,会用同一个ID答复,便于请求响应关联 | Int | Y | - | +| data | 请求参数,请参考同名接口的HTTP接口文档,与HTTP接口完全一致 | Json | Y | - | + +###### 请求返回结果: + +```json +{ + "data": { + "version": "v3.9.0-beta" + }, + "command": "getVersion", + "msgId": 114514, + "code": 0, + "message": "success" +} +``` + +| 字段名称 | 字段说明 | 类型 | 备注 | +|------|-----------------------------------|--------|---- | +| command | 操作命令,与请求一致 | String | - | +| msgId | 消息ID,此次请求的消息ID为114514则响应也是114514 | Int | - | +| data | 响应内容,请参考同名接口的HTTP接口文档,与HTTP接口完全一致 | Json | - | + +--- + +##### 接口名称 创建连接 + +```json +{ + "command": "connect", + "msgId": 114514, + "data": { + "adbPath": "C:\\Users\\atmzx\\Desktop\\MeoAssistantArknights3\\platform-tools\\adb.exe", + "host": "127.0.0.1:62001" + } +} +``` + +| 字段名称 | 字段说明 | 类型 | 必填 | 备注 | +| ---- |---------------------------------|--------|---- |-----| +| command | 操作命令 | String |,Y | - | +| msgId | 消息ID,服务端收到此次请求,会用同一个ID答复,便于请求响应关联 | Int | Y | - | +| data | 请求参数,请参考同名接口的HTTP文档,与HTTP接口完全一致 | Json | Y | - | + +###### 请求返回结果: + +```json +{ + "data": { + "id": "46b9d5cd60382f100b336b17d6817f8eda255f73", + "result": true + }, + "command": "connect", + "msgId": 114514, + "code": 0, + "message": "success" +} +``` + +| 字段名称 | 字段说明 | 类型 | 备注 | +|------|-----------------------------------|--------|---- | +| command | 操作命令,与请求一致 | String | - | +| msgId | 消息ID,此次请求的消息ID为114514则响应也是114514 | Int | - | +| data | 响应内容,请参考同名接口的HTTP接口文档,与HTTP接口完全一致 | Json | - | + +appendTask setTaskParams start stop等接口不再描述,均可遵循以上规则,参考HTTP接口文档实现。 + + +--- + +##### 接口名称 回调消息 + +###### 返回结果示例: + +```json +{ + "data": { + "id": "46b9d5cd60382f100b336b17d6817f8eda255f73", + "logId": 1, + "msg": 2, + "details": { + "uuid": "", + "details": { + "adb": "C:\\MeoAssistantArknights3\\platform-tools\\adb.exe", + "address": "127.0.0.1:62001", + "config": "General", + "width": 1280, + "height": 720 + }, + "what": "ResolutionGetted", + "why": "" + } + }, + "command": "callBack", + "msgId": 0, + "code": 0, + "message": "success" +} +``` + +| 字段名称 | 字段说明 | 类型 | 备注 | +|------|-----------------------------------|--------|---- | +| command | 操作命令,callBack类型 | String | - | +| msgId | 消息ID,callBack 一律为0 | Int | - | +| data | 响应内容,详情参考[集成文档](https://raw.githubusercontent.com/MaaAssistantArknights/MaaAssistantArknights/master/docs/集成文档.md) | Json | - | diff --git a/src/Java/Maaj/build.gradle.kts b/src/Java/Maaj/build.gradle.kts index 2455e9477f..d70713c67c 100644 --- a/src/Java/Maaj/build.gradle.kts +++ b/src/Java/Maaj/build.gradle.kts @@ -1,18 +1,103 @@ +val ktor_version = "2.0.2" +val kotlin_version = "1.6.10" +val logback_version = "1.2.11" +val appName = "Maa-HTTP" + plugins { - id("java") + application + kotlin("jvm") version "1.6.10" + kotlin("plugin.serialization") version "1.6.10" + id("edu.sc.seis.launch4j") version "2.5.3" } -version = "1.0-SNAPSHOT" +group = "com.iguigui" +version = "0.0.1" +application { + mainClass.set("com.iguigui.maaj.ApplicationKt") + val isDevelopment: Boolean = project.ext.has("development") + applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") +} repositories { mavenCentral() + maven { url = uri("https://maven.pkg.jetbrains.space/public/p/ktor/eap") } } dependencies { - //JNA implementation("net.java.dev.jna:jna:5.11.0") + implementation("io.ktor:ktor-server-core-jvm:$ktor_version") + implementation("io.ktor:ktor-server-netty-jvm:$ktor_version") + implementation("io.ktor:ktor-server-content-negotiation:$ktor_version") + implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version") + implementation("io.ktor:ktor-server-cors:$ktor_version") + implementation("io.ktor:ktor-server-websockets:$ktor_version") + implementation("ch.qos.logback:logback-classic:$logback_version") + implementation("io.ktor:ktor-server-call-logging:$ktor_version") + implementation("io.ktor:ktor-server-double-receive:$ktor_version") + testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version") + testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3") } -tasks.getByName("test") { - useJUnitPlatform() + + +tasks.jar { + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + + manifest { + attributes( + mapOf( + "Main-Class" to "com.iguigui.maaj.ApplicationKt", + "Implementation-Title" to "Maa-HTTP", + "Manifest-Version" to "1.0.0" + ) + ) + } + + from(configurations.runtimeClasspath.get().map { + if (it.isDirectory) it else zipTree(it) + }) + val sourcesMain = sourceSets.main.get() + sourcesMain.allSource.forEach { println("add from sources: ${it.name}") } + from(sourcesMain.output) +} + +launch4j { + mainClassName = "com.iguigui.maaj.ApplicationKt" + icon = "${projectDir}/icons/myApp.ico" +} + +tasks.withType { + outfile = "${appName}.exe" + mainClassName = "com.iguigui.maaj.ApplicationKt" + icon = "$projectDir/icon.ico" + productName = "${appName}" +} + +tasks.register("createFastStart") { + outfile = "${appName}.exe" + mainClassName = "com.iguigui.maaj.ApplicationKt" + icon = "$projectDir/icon.ico" + fileDescription = "The lightning fast implementation" +} +tasks.register("MyApp-memory") { + fileDescription = "The default implementation with increased heap size" + maxHeapPercent = 50 +} + + +tasks.withType { + options.encoding = "UTF-8" +} + +val compileKotlin: org.jetbrains.kotlin.gradle.tasks.KotlinCompile by tasks +compileKotlin.kotlinOptions { + jvmTarget = "1.8" +} + + +val compileTestKotlin: org.jetbrains.kotlin.gradle.tasks.KotlinCompile by tasks +compileTestKotlin.kotlinOptions { + jvmTarget = "1.8" } diff --git a/src/Java/Maaj/gradlew b/src/Java/Maaj/gradlew index 1b6c787337..744e882ed5 100644 --- a/src/Java/Maaj/gradlew +++ b/src/Java/Maaj/gradlew @@ -1,7 +1,7 @@ -#!/bin/sh +#!/usr/bin/env sh # -# Copyright © 2015-2021 the original authors. +# Copyright 2015 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,101 +17,67 @@ # ############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# +## +## Gradle start up script for UN*X +## ############################################################################## # Attempt to set APP_HOME - # Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi done - -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" -APP_BASE_NAME=${0##*/} +APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum +MAX_FD="maximum" warn () { echo "$*" -} >&2 +} die () { echo echo "$*" echo exit 1 -} >&2 +} # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -121,9 +87,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java + JAVACMD="$JAVA_HOME/jre/sh/java" else - JAVACMD=$JAVA_HOME/bin/java + JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -132,7 +98,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD=java + JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -140,95 +106,80 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi fi -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi # For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=$( cygpath --unix "$JAVACMD" ) + JAVACMD=`cygpath --unix "$JAVACMD"` - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" diff --git a/src/Java/Maaj/settings.gradle.kts b/src/Java/Maaj/settings.gradle.kts index 2225483988..5a08376e8f 100644 --- a/src/Java/Maaj/settings.gradle.kts +++ b/src/Java/Maaj/settings.gradle.kts @@ -1,2 +1,16 @@ -rootProject.name = "Maaj" +pluginManagement { + repositories { + maven { url = uri("https://repo.spring.io/milestone") } + maven { url = uri("https://repo.spring.io/snapshot") } + gradlePluginPortal() + } + resolutionStrategy { + eachPlugin { + if (requested.id.id == "org.springframework.boot") { + useModule("org.springframework.boot:spring-boot-gradle-plugin:${requested.version}") + } + } + } +} +rootProject.name = "Maa-HTTP" diff --git a/src/Java/Maaj/src/main/java/com/iguigui/maaj/Application.kt b/src/Java/Maaj/src/main/java/com/iguigui/maaj/Application.kt new file mode 100644 index 0000000000..fb8d51edbf --- /dev/null +++ b/src/Java/Maaj/src/main/java/com/iguigui/maaj/Application.kt @@ -0,0 +1,80 @@ +package com.iguigui.maaj + +import com.iguigui.maaj.routing.httpRouting +import com.iguigui.maaj.routing.wsRouting +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import io.ktor.server.netty.* +import io.ktor.server.application.* +import io.ktor.server.plugins.callloging.* +import io.ktor.server.plugins.contentnegotiation.* +import io.ktor.server.plugins.cors.routing.* +import io.ktor.server.plugins.doublereceive.* +import io.ktor.server.request.* +import io.ktor.server.websocket.* +import io.ktor.util.logging.* +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import org.slf4j.event.Level + + +fun main(args: Array): Unit = EngineMain.main(args) + +fun Application.module() { + configure() + httpRouting() + wsRouting() +} + + +lateinit var logger : Logger + +fun Application.configure() { + logger = log + //Input and output serialize + install(ContentNegotiation) { + json(Json { + encodeDefaults = true + isLenient = true + allowSpecialFloatingPointValues = true + allowStructuredMapKeys = true + prettyPrint = false + useArrayPolymorphism = false + }) + } + install(CORS) { + allowCredentials = true + anyHost() + } + install(WebSockets) { + pingPeriod = java.time.Duration.ofSeconds(15) + timeout = java.time.Duration.ofSeconds(15) + maxFrameSize = Long.MAX_VALUE + masking = false + } + //Provides the ability to receive a request body several times. + install(DoubleReceive) { + } + //Logging request body. + install(CallLogging) { + level = Level.INFO + format { call -> + val status = call.response.status() + val httpMethod = call.request.httpMethod + val queryParameters = call.request.rawQueryParameters + when (httpMethod) { + HttpMethod.Get -> "Status: $status, HTTP method: $httpMethod, Uri: ${call.request.uri}, QueryParameters: $queryParameters " + HttpMethod.Post -> runBlocking { + "Status: $status, HTTP method: $httpMethod, Uri: ${call.request.uri} ,Body: ${ + call.receive().toString() + } " + } + else -> { + "Status: $status, HTTP method: $httpMethod, Uri: ${call.request.uri}, QueryParameters: $queryParameters " + } + } + } + } + +} diff --git a/src/Java/Maaj/src/main/java/com/iguigui/maaj/dto/msgEnum.kt b/src/Java/Maaj/src/main/java/com/iguigui/maaj/dto/msgEnum.kt new file mode 100644 index 0000000000..b990ffabf4 --- /dev/null +++ b/src/Java/Maaj/src/main/java/com/iguigui/maaj/dto/msgEnum.kt @@ -0,0 +1,19 @@ +package com.iguigui.maaj.dto + +/* Global Info */ +val INTERNAL_ERROR = 0 // 内部错误 +val INIT_FAILED = 1 // 初始化失败 +val CONNECTION_INFO = 2 // 连接相关信息 +val ALL_TASKS_COMPLETED = 3 // 全部任务完成 + +/* TaskChain Info */ +val TASK_CHAIN_ERROR = 10000 // 任务链执行/识别错误 +val TASK_CHAIN_START = 10001 // 任务链开始 +val TASK_CHAIN_COMPLETED = 10002 // 任务链完成 +val TASK_CHAIN_EXTRA_INFO = 10003 // 任务链额外信息 + +/* SubTask Info */ +val SUB_TASK_ERROR = 20000 // 原子任务执行/识别错误 +val SUB_TASK_START = 20001 // 原子任务开始 +val SUB_TASK_COMPLETED = 20002 // 原子任务完成 +val SUB_TASK_EXTRA_INFO = 20003 // 原子任务额外信息 diff --git a/src/Java/Maaj/src/main/java/com/iguigui/maaj/dto/request.kt b/src/Java/Maaj/src/main/java/com/iguigui/maaj/dto/request.kt new file mode 100644 index 0000000000..24127bdddd --- /dev/null +++ b/src/Java/Maaj/src/main/java/com/iguigui/maaj/dto/request.kt @@ -0,0 +1,179 @@ +package com.iguigui.maaj.dto + +import com.iguigui.maaj.util.Json +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.JsonElement + + +@Serializable +data class ConnectRequest( + @SerialName("adbPath") + val adbPath: String, + @SerialName("host") + val host: String, + @SerialName("detailJson") + val detailJson: String = "" +) + +@Serializable +data class AppendTaskRequest( + @SerialName("id") + val id: String, + @SerialName("type") + val type: String, + @SerialName("params") + val params: JsonElement +) + +@Serializable +data class SetTaskParamsRequest( + @SerialName("id") + val id: String, + @SerialName("type") + val type: String, + @SerialName("taskId") + val taskId: Int, + @SerialName("params") + val params: JsonElement +) + +@Serializable +sealed class Task + +@Serializable +data class Fight( + @SerialName("medicine") + val medicine: Int?, + @SerialName("penguin_id") + val penguinId: String?, + @SerialName("report_to_penguin") + val reportToPenguin: Boolean?, + @SerialName("server") + val server: String?, + @SerialName("stage") + val stage: String?, + @SerialName("stone") + val stone: Int?, + @SerialName("times") + val times: Int? +) : Task() + + +@Serializable +data class Recruit( + @SerialName("confirm") + val confirm: List, + @SerialName("expedite") + val expedite: Boolean?, + @SerialName("expedite_times") + val expediteTimes: Int?, + @SerialName("refresh") + val refresh: Boolean?, + @SerialName("select") + val select: List, + @SerialName("set_time") + val setTime: Boolean?, + @SerialName("times") + val times: Int? +) : Task() + + +@Serializable +data class Infrast( + @SerialName("drones") + val drones: String?, + @SerialName("facility") + val facility: List, + @SerialName("mode") + val mode: Int?, + @SerialName("replenish") + val replenish: Boolean?, + @SerialName("threshold") + val threshold: Double? +) : Task() + + +@Serializable +class Visit : Task() + +@Serializable +data class Mall( + @SerialName("blacklist") + val blacklist: List?, + @SerialName("buy_first") + val buyFirst: List?, + @SerialName("shopping") + val shopping: Boolean? +) : Task() + + +@Serializable +class Award : Task() + +@Serializable +data class Roguelike( + @SerialName("mode") + val mode: Int?, + @SerialName("opers") + val opers: List +) : Task() + + +@Serializable +data class Oper( + @SerialName("name") + val name: String, + @SerialName("skill") + val skill: Int?, + @SerialName("skill_usage") + val skillUsage: Int? +) + +@Serializable +data class Copilot( + @SerialName("filename") + val filename: String, + @SerialName("formation") + val formation: Boolean, + @SerialName("stage_name") + val stageName: String? +) + + + +@Serializable +data class StartRequest( + @SerialName("id") + val id: String, +) + +@Serializable +data class StopRequest( + @SerialName("id") + val id: String, +) + +@Serializable +data class DestroyRequest( + @SerialName("id") + val id: String, +) + +@SerialName("connect") +@Serializable +data class WsRequest( + @SerialName("data") + val `data`: JsonElement, + @SerialName("command") + val command: String, + @SerialName("msgId") + val msgId: Int = 0 +) + + +fun Task.toJsonString() = Json.encodeToString(this) + + + diff --git a/src/Java/Maaj/src/main/java/com/iguigui/maaj/dto/response.kt b/src/Java/Maaj/src/main/java/com/iguigui/maaj/dto/response.kt new file mode 100644 index 0000000000..3322f86039 --- /dev/null +++ b/src/Java/Maaj/src/main/java/com/iguigui/maaj/dto/response.kt @@ -0,0 +1,141 @@ +package com.iguigui.maaj.dto + +import com.iguigui.maaj.util.Json +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.encodeToJsonElement +import kotlinx.serialization.json.jsonObject + + +@Serializable +data class HttpResponse( + @SerialName("data") + val data: JsonElement = EmptyBaseData.toJsonElement(), + @SerialName("code") + val code: Int = 0, + @SerialName("message") + val message: String = "success" +) + +@Serializable +data class WsResponse( + @SerialName("data") + val data: JsonElement, + @SerialName("command") + val command: String, + @SerialName("msgId") + val msgId: Int = 0, + @SerialName("code") + val code: Int = 0, + @SerialName("message") + val message: String = "success" +) + +@Serializable +sealed class BaseData + +@Serializable +data class GetVersionResponse( + @SerialName("version") + val version: String +) : BaseData() + +@Serializable +data class ConnectResponse( + @SerialName("id") + val id: String, + @SerialName("result") + val result: Boolean +) : BaseData() + + +@Serializable +data class ListInstanceResponse( + @SerialName("list") + val list: List +) : BaseData() + +@Serializable +data class MaaInstanceInfo( + @SerialName("id") + val id: String, + @SerialName("host") + val host: String, + @SerialName("adbPath") + val adbPath: String, + @SerialName("uuid") + val uuid: String, + @SerialName("status") + val status: Int, +) : BaseData() + +@Serializable +data class AppendTaskResponse( + @SerialName("id") + val id: String, + @SerialName("taskId") + val taskId: Int +) : BaseData() + + +@Serializable +data class SetTaskParamsResponse( + @SerialName("id") + val id: String, + @SerialName("result") + val result: Boolean +) : BaseData() + + +@Serializable +data class StartResponse( + @SerialName("id") + val id: String, + @SerialName("result") + val result: Boolean +) : BaseData() + +@Serializable +data class StopResponse( + @SerialName("id") + val id: String, + @SerialName("result") + val result: Boolean +) : BaseData() + + + +@Serializable +data class CallBackLog( + @SerialName("id") + val id: String, + @SerialName("logId") + val logId: Long, + @SerialName("msg") + val msg: Int, + @SerialName("details") + val details: JsonElement, +) : BaseData() + + +@Serializable +object EmptyBaseData : BaseData() + +fun BaseData.toJsonElement() = + Json.encodeToJsonElement(Json.encodeToJsonElement(this).jsonObject.filterNot { it.key == "type" }) + + +fun BaseData.wapperToHttpResponse() = + HttpResponse(Json.encodeToJsonElement(Json.encodeToJsonElement(this).jsonObject.filterNot { it.key == "type" })) + +fun BaseData.wapperToWsResponse(command: String, msgId: Int) = + WsResponse( + Json.encodeToJsonElement(Json.encodeToJsonElement(this).jsonObject.filterNot { it.key == "type" }), + command, + msgId + ) + +fun WsResponse.toJsonString() = Json.encodeToJsonElement(Json.encodeToJsonElement(this).jsonObject.filterNot { it.key == "type" }).toString() + diff --git a/src/Java/Maaj/src/main/java/com/iguigui/maaj/MaaJavaSample.java b/src/Java/Maaj/src/main/java/com/iguigui/maaj/easySample/MaaJavaSample.java similarity index 95% rename from src/Java/Maaj/src/main/java/com/iguigui/maaj/MaaJavaSample.java rename to src/Java/Maaj/src/main/java/com/iguigui/maaj/easySample/MaaJavaSample.java index 92d5383dbb..9a8de744a4 100644 --- a/src/Java/Maaj/src/main/java/com/iguigui/maaj/MaaJavaSample.java +++ b/src/Java/Maaj/src/main/java/com/iguigui/maaj/easySample/MaaJavaSample.java @@ -1,4 +1,4 @@ -package com.iguigui.maaj; +package com.iguigui.maaj.easySample; import com.sun.jna.Native; import com.sun.jna.Pointer; @@ -8,7 +8,7 @@ import java.util.Scanner; public class MaaJavaSample { /** - * Maa的Java调用实例实现,项目需依赖JNA库。 + * Maa的Java调用实例实现,最简示例仅需依赖第三方JNA库。 * 仅在Windows下进行过测试,未在Linux下测试过(相信强大的Linux用户自己可以解决问题! * * --------特别提醒---------- diff --git a/src/Java/Maaj/src/main/java/com/iguigui/maaj/MeoAssistant.java b/src/Java/Maaj/src/main/java/com/iguigui/maaj/easySample/MeoAssistant.java similarity index 98% rename from src/Java/Maaj/src/main/java/com/iguigui/maaj/MeoAssistant.java rename to src/Java/Maaj/src/main/java/com/iguigui/maaj/easySample/MeoAssistant.java index 3802ea04e0..d188eb5d3f 100644 --- a/src/Java/Maaj/src/main/java/com/iguigui/maaj/MeoAssistant.java +++ b/src/Java/Maaj/src/main/java/com/iguigui/maaj/easySample/MeoAssistant.java @@ -1,4 +1,4 @@ -package com.iguigui.maaj; +package com.iguigui.maaj.easySample; import com.sun.jna.Pointer; import com.sun.jna.win32.StdCallLibrary; diff --git a/src/Java/Maaj/src/main/java/com/iguigui/maaj/routing/HttpRouting.kt b/src/Java/Maaj/src/main/java/com/iguigui/maaj/routing/HttpRouting.kt new file mode 100644 index 0000000000..fe3e5baaf7 --- /dev/null +++ b/src/Java/Maaj/src/main/java/com/iguigui/maaj/routing/HttpRouting.kt @@ -0,0 +1,68 @@ +package com.iguigui.maaj.routing + +import com.iguigui.maaj.dto.* +import com.iguigui.maaj.service.MaaService +import com.iguigui.maaj.service.MaaService.appendTask +import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import kotlinx.serialization.json.jsonObject + + +fun Application.httpRouting() { + + routing { + route("/API/V1") { + get("/getVersion") { + val version = MaaService.getVersion() + call.respond(GetVersionResponse(version).wapperToHttpResponse()) + } + post("/connect") { + with(call.receive()) { + val connect = MaaService.connect(adbPath, host, detailJson) + call.respond(connect.wapperToHttpResponse()) + } + } + post("/appendTask") { + with(call.receive()) { + val result = appendTask( + id, + type, + params.jsonObject.toString() + ) + call.respond(AppendTaskResponse(id, result).wapperToHttpResponse()) + } + } + post("/setTaskParams") { + with(call.receive()) { + val result = MaaService.setTaskParams( + id, + taskId, + params.jsonObject.toString() + ) + call.respond(SetTaskParamsResponse(id, result).wapperToHttpResponse()) + } + } + post("/start") { + val startRequest = call.receive() + val result = MaaService.start(startRequest.id) + call.respond(StartResponse(startRequest.id, result).wapperToHttpResponse()) + } + post("/stop") { + val stopRequest = call.receive() + val result = MaaService.stop(stopRequest.id) + call.respond(StartResponse(stopRequest.id, result).wapperToHttpResponse()) + } + post("/destroy") { + val destroy = call.receive() + MaaService.destroy(destroy.id) + call.respond(EmptyBaseData.wapperToHttpResponse()) + } + get("/listInstance") { + val list = MaaService.listInstance() + call.respond(ListInstanceResponse(list).wapperToHttpResponse()) + } + } + } +} diff --git a/src/Java/Maaj/src/main/java/com/iguigui/maaj/routing/WsRouting.kt b/src/Java/Maaj/src/main/java/com/iguigui/maaj/routing/WsRouting.kt new file mode 100644 index 0000000000..52f1020286 --- /dev/null +++ b/src/Java/Maaj/src/main/java/com/iguigui/maaj/routing/WsRouting.kt @@ -0,0 +1,101 @@ +package com.iguigui.maaj.routing + +import com.iguigui.maaj.dto.* +import com.iguigui.maaj.logger +import com.iguigui.maaj.service.Connection +import com.iguigui.maaj.service.MaaService +import com.iguigui.maaj.service.MaaService.addConnection +import com.iguigui.maaj.service.MaaService.appendTask +import com.iguigui.maaj.service.MaaService.removeConnection +import com.iguigui.maaj.util.Json +import io.ktor.websocket.* +import io.ktor.server.application.* +import io.ktor.server.routing.* +import io.ktor.server.websocket.* +import io.ktor.websocket.* +import kotlinx.serialization.json.jsonObject +import java.util.* +import kotlin.collections.LinkedHashSet + + +fun Application.wsRouting() { + routing { + webSocket("/API/V1") { + val connection = Connection(this) + logger.info("Ws connection connected ! connection : ${connection.session} ") + addConnection(connection) + try { + for (frame in incoming) { + frame as? Frame.Text ?: continue + val receivedText = frame.readText() + logger.info(receivedText) + val wsRequest = Json.decodeFromString(WsRequest.serializer(), receivedText) + var response: BaseData? = null + when (wsRequest.command) { + "getVersion" -> { + response = GetVersionResponse(MaaService.getVersion()) + } + "connect" -> { + with(Json.decodeFromJsonElement(ConnectRequest.serializer(), wsRequest.data)) { + response = MaaService.connect(adbPath, host, detailJson) + } + } + "appendTask" -> { + with(Json.decodeFromJsonElement(AppendTaskRequest.serializer(), wsRequest.data)) { + val result = appendTask( + id, + type, + params.jsonObject.toString() + ) + response = AppendTaskResponse(id, result) + } + } + "setTaskParams" -> { + with(Json.decodeFromJsonElement(SetTaskParamsRequest.serializer(), wsRequest.data)) { + val result = MaaService.setTaskParams( + id, + taskId, + params.jsonObject.toString() + ) + response = SetTaskParamsResponse(id, result) + } + } + "start" -> { + val startRequest = + Json.decodeFromJsonElement(StartRequest.serializer(), wsRequest.data) + val result = MaaService.start(startRequest.id) + response = StartResponse(startRequest.id, result) + } + "stop" -> { + val stopRequest = + Json.decodeFromJsonElement(StopRequest.serializer(), wsRequest.data) + val result = MaaService.stop(stopRequest.id) + response = StopResponse(stopRequest.id, result) + } + "destroy" -> { + val destroyRequest = + Json.decodeFromJsonElement(DestroyRequest.serializer(), wsRequest.data) + MaaService.destroy(destroyRequest.id) + response = EmptyBaseData + } + "listInstance" -> { + val list = MaaService.listInstance() + response = ListInstanceResponse(list) + } + else -> {} + } + response?.let { + send(it.wapperToWsResponse(wsRequest.command, wsRequest.msgId).toJsonString()) + } + + } + } catch (e: Exception) { + logger.warn("Ws Exception $e.message") + logger.warn(e.stackTraceToString()) + } finally { + logger.info("Ws connection disconnected ! connection : $connection ") + removeConnection(connection) + } + } + } +} diff --git a/src/Java/Maaj/src/main/java/com/iguigui/maaj/service/Connection.kt b/src/Java/Maaj/src/main/java/com/iguigui/maaj/service/Connection.kt new file mode 100644 index 0000000000..ea1cf504ae --- /dev/null +++ b/src/Java/Maaj/src/main/java/com/iguigui/maaj/service/Connection.kt @@ -0,0 +1,11 @@ +package com.iguigui.maaj.service + +import io.ktor.websocket.* +import java.util.concurrent.atomic.AtomicInteger + +class Connection(val session: DefaultWebSocketSession) { + companion object { + var lastId = AtomicInteger(0) + } + val name = "user${lastId.getAndIncrement()}" +} diff --git a/src/Java/Maaj/src/main/java/com/iguigui/maaj/service/MaaInstance.kt b/src/Java/Maaj/src/main/java/com/iguigui/maaj/service/MaaInstance.kt new file mode 100644 index 0000000000..7209468f40 --- /dev/null +++ b/src/Java/Maaj/src/main/java/com/iguigui/maaj/service/MaaInstance.kt @@ -0,0 +1,85 @@ +package com.iguigui.maaj.service + +import com.iguigui.maaj.dto.CallBackLog +import com.iguigui.maaj.dto.* +import com.iguigui.maaj.easySample.MeoAssistant +import com.iguigui.maaj.logger +import com.iguigui.maaj.util.Json +import com.sun.jna.Pointer +import io.ktor.server.application.* +import java.util.concurrent.atomic.AtomicLong +import kotlin.reflect.KFunction1 + +class MaaInstance( + private val instance: MeoAssistant, + val id: String, + val adbPath: String, + val host: String, + private val detailJson: String, + val callBackAction: KFunction1 +) : + MeoAssistant.AsstApiCallback { + +// constructor( +// instance: MeoAssistant, +// id: String, +// adbPath: String, +// host: String, +// pointer: Pointer, +// detailJson: String +// ) : this( +// instance, +// id, +// adbPath, +// host, +// detailJson +// ) { +// this.pointer = pointer +// } + + lateinit var pointer: Pointer + + var uuid = "" + + //状态码,0初始化未运行 1运行中 + var status = 0 + + // private var logQueue: ArrayBlockingQueue = ArrayBlockingQueue(100) +// + private var msgId = AtomicLong(0) + + + override fun callback(msg: Int, detail_json: String, custom_arg: String) { + val callBackLog = CallBackLog(id, msgId.getAndIncrement(), msg, Json.parseToJsonElement(detail_json)) + logger.info("callBackLog = $callBackLog") + callBackAction.call(callBackLog) + when (msg) { + INTERNAL_ERROR, + INIT_FAILED, + ALL_TASKS_COMPLETED, + TASK_CHAIN_ERROR, + TASK_CHAIN_COMPLETED, + SUB_TASK_ERROR, + SUB_TASK_COMPLETED -> status = 0 + //也不知道啥算正在运行,姑且认为任务链开始和原子任务开始肯定是在运行中吧 + TASK_CHAIN_START, + SUB_TASK_START -> status = 1 + else -> {} + } + } + + fun connect(): Boolean = instance.AsstConnect(pointer, adbPath, host, detailJson) + + fun appendTask(type: String, params: String) = instance.AsstAppendTask(pointer, type, params) + + fun setTaskParams(taskId: Int, params: String) = instance.AsstSetTaskParams(pointer, taskId, params) + + fun start(): Boolean { + msgId.set(0) + return instance.AsstStart(pointer) + } + + fun stop() = instance.AsstStop(pointer) + fun destroy() = instance.AsstDestroy(pointer) + +} diff --git a/src/Java/Maaj/src/main/java/com/iguigui/maaj/service/MaaService.kt b/src/Java/Maaj/src/main/java/com/iguigui/maaj/service/MaaService.kt new file mode 100644 index 0000000000..16645b345c --- /dev/null +++ b/src/Java/Maaj/src/main/java/com/iguigui/maaj/service/MaaService.kt @@ -0,0 +1,98 @@ +package com.iguigui.maaj.service + +import com.iguigui.maaj.dto.* +import com.iguigui.maaj.easySample.MeoAssistant +import com.iguigui.maaj.logger +import com.sun.jna.Native +import io.ktor.websocket.* +import kotlinx.coroutines.runBlocking +import java.io.File +import java.security.MessageDigest +import java.util.* +import java.util.concurrent.ConcurrentHashMap + + +object MaaService { + + private var instancePool: ConcurrentHashMap = ConcurrentHashMap() + + private val wsConnection = Collections.synchronizedSet(LinkedHashSet()) + + val meoAssistant: MeoAssistant by lazy { + var maaPath = File(File("").absolutePath).parent + logger.info("maaPath $maaPath") +// maaPath = "C:\\Users\\atmzx\\Desktop\\MeoAssistantArknights3" + System.setProperty("jna.library.path", maaPath) + val load = Native.load("MeoAssistant", MeoAssistant::class.java) + load.AsstLoadResource(maaPath) + load + } + + fun connect(adbPath: String, host: String, detailJson: String): ConnectResponse { + val id = sha1(host) +// if (instancePool.containsKey(id)) { +// return ConnectResponse(id, true) +// } + val maaInstance = MaaInstance(meoAssistant, id, adbPath, host, detailJson, ::callBackLog) + maaInstance.pointer = meoAssistant.AsstCreateEx(maaInstance, maaInstance.id) + val connect = maaInstance.connect() + if (!connect) { + return ConnectResponse("", false) + } + instancePool[id] = maaInstance + return ConnectResponse(id, true) + } + + fun appendTask(id: String, type: String, detailJson: String) = + instancePool[id]?.appendTask(type, detailJson) ?: 0 + + + fun setTaskParams(id: String, taskId: Int, detailJson: String) = + instancePool[id]?.setTaskParams(taskId, detailJson) ?: false + + fun start(id: String) = instancePool[id]?.start() ?: false + + fun stop(id: String) = instancePool[id]?.stop() ?: false + + fun getVersion(): String = meoAssistant.AsstGetVersion() + + private fun sha1(password: String): String { + val messageDigest = MessageDigest.getInstance("SHA") + val digests = messageDigest.digest(password.toByteArray()) + val stringBuilder = StringBuilder() + for (i in digests.indices) { + var halfbyte = digests[i].toInt() ushr 4 and 0x0F + for (j in 0..1) { + stringBuilder.append( + if (0 <= halfbyte && halfbyte <= 9) ('0'.code + halfbyte).toChar() else ('a'.code + (halfbyte - 10)).toChar() + ) + halfbyte = digests[i].toInt() and 0x0F + } + } + return stringBuilder.toString() + } + + fun listInstance(): List = + instancePool.values.map { e -> MaaInstanceInfo(e.id, e.host, e.adbPath, e.uuid, e.status) }.toList() + + fun destroy(id: String) { + instancePool[id]?.destroy() + instancePool.remove(id) + } + + + fun callBackLog(message: CallBackLog) { + runBlocking { + wsConnection.forEach { it.session.send(message.wapperToWsResponse("callBack", 0).toJsonString()) } + } + } + + fun addConnection(connection: Connection) { + this.wsConnection += connection + } + + fun removeConnection(connection: Connection) { + this.wsConnection -= connection + } + +} diff --git a/src/Java/Maaj/src/main/java/com/iguigui/maaj/util/JsonUtil.kt b/src/Java/Maaj/src/main/java/com/iguigui/maaj/util/JsonUtil.kt new file mode 100644 index 0000000000..1f89ee0eca --- /dev/null +++ b/src/Java/Maaj/src/main/java/com/iguigui/maaj/util/JsonUtil.kt @@ -0,0 +1,10 @@ +package com.iguigui.maaj.util + +import kotlinx.serialization.json.Json + +val Json by lazy { + Json { + ignoreUnknownKeys = true + encodeDefaults = true + } +} diff --git a/src/Java/Maaj/src/main/resources/application.conf b/src/Java/Maaj/src/main/resources/application.conf new file mode 100644 index 0000000000..cc5a87ccdc --- /dev/null +++ b/src/Java/Maaj/src/main/resources/application.conf @@ -0,0 +1,8 @@ +ktor { + deployment { + port = 8848 + } + application { + modules = [ com.iguigui.maaj.ApplicationKt.module ] + } +} diff --git a/src/Java/Maaj/src/main/resources/log4j2.xml b/src/Java/Maaj/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..c84ff1d089 --- /dev/null +++ b/src/Java/Maaj/src/main/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/Java/Maaj/src/main/resources/logback-fileAppender.xml b/src/Java/Maaj/src/main/resources/logback-fileAppender.xml new file mode 100644 index 0000000000..9a1d027bf3 --- /dev/null +++ b/src/Java/Maaj/src/main/resources/logback-fileAppender.xml @@ -0,0 +1,13 @@ + + + Maaj.log + true + + %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/src/Java/Maaj/src/main/resources/logback.xml b/src/Java/Maaj/src/main/resources/logback.xml new file mode 100644 index 0000000000..8cadd778e5 --- /dev/null +++ b/src/Java/Maaj/src/main/resources/logback.xml @@ -0,0 +1,32 @@ + + + + %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + log/maa-http.log + + + maa-http-%d{yyyy-MM-dd}.%i.txt + + 10MB + 10 + 100MB + + + true + + %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/src/Java/Maaj/src/test/java/com/iguigui/maaj/routing/HttpRoutingKtTest.kt b/src/Java/Maaj/src/test/java/com/iguigui/maaj/routing/HttpRoutingKtTest.kt new file mode 100644 index 0000000000..bfb1401860 --- /dev/null +++ b/src/Java/Maaj/src/test/java/com/iguigui/maaj/routing/HttpRoutingKtTest.kt @@ -0,0 +1,95 @@ +package com.iguigui.maaj.routing; + +import com.iguigui.maaj.configure +import com.iguigui.maaj.dto.GetVersionResponse +import io.ktor.client.call.* +import io.ktor.client.request.* +import io.ktor.http.HttpStatusCode.Companion.OK +import io.ktor.server.testing.* +import org.junit.Assert +import org.junit.Before +import kotlin.test.Test + +class HttpRoutingKtTest { + + +// @Test +// fun testGetApiV1Getversion() = testApplication { +// application { +// httpRouting() +// } +// client.get("/API/V1/getVersion").apply { +// Assert.assertEquals(status,OK) +// } +// } +// +// @Test +// fun testPostApiV1Connect() = testApplication { +// application { +// httpRouting() +// } +// client.post("/API/V1/connect").apply { +// +// } +// } +// +// @Test +// fun testPostApiV1Appendtask() = testApplication { +// application { +// httpRouting() +// } +// client.post("/API/V1/appendTask").apply { +// TODO("Please write your test here") +// } +// } +// +// @Test +// fun testPostApiV1Settaskparams() = testApplication { +// application { +// httpRouting() +// } +// client.post("/API/V1/setTaskParams").apply { +// TODO("Please write your test here") +// } +// } +// +// @Test +// fun testPostApiV1Start() = testApplication { +// application { +// httpRouting() +// } +// client.post("/API/V1/start").apply { +// TODO("Please write your test here") +// } +// } +// +// @Test +// fun testPostApiV1Stop() = testApplication { +// application { +// httpRouting() +// } +// client.post("/API/V1/stop").apply { +// TODO("Please write your test here") +// } +// } +// +// @Test +// fun testPostApiV1Destroy() = testApplication { +// application { +// httpRouting() +// } +// client.post("/API/V1/destroy").apply { +// TODO("Please write your test here") +// } +// } +// +// @Test +// fun testGetApiV1Listinstance() = testApplication { +// application { +// httpRouting() +// } +// client.get("/API/V1/listInstance").apply { +// TODO("Please write your test here") +// } +// } +}