diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..06c03a0 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,32 @@ +name: Build YeeeesMOTD +on: + - push +jobs: + build: + strategy: + matrix: + java: + - 21 + os: + - ubuntu-latest + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Validate gradle wrapper + uses: gradle/wrapper-validation-action@v1 + - name: Setup jdk ${{ matrix.java }} + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + - name: Make Gradle wrapper executable + run: chmod +x ./gradlew + - name: Build + run: ./gradlew build + - name: Deploy Bot to MCSM + run: ./gradlew deployBot + env: + MCSM_API_KEY: ${{ secrets.MCSM_API_KEY }} + MCSM_API_URL: ${{ secrets.MCSM_API_URL }} + MCSM_DAEMON_ID: ${{ secrets.MCSM_DAEMON_ID }} + MCSM_INSTANCE_ID: ${{ secrets.MCSM_INSTANCE_ID }} diff --git a/build.gradle.kts b/build.gradle.kts index c495a73..fe71c67 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,4 @@ +import buildsrc.MCSMDeploy import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { @@ -52,4 +53,12 @@ tasks.shadowJar { "Manifest-Version" to "1.0" ) } +} + +tasks.register("deployBot") { + val apiKey = System.getenv("MCSM_API_KEY") + val apiUrl = System.getenv("MCSM_API_URL") + val daemonId = System.getenv("MCSM_DAEMON_ID") + val instanceId = System.getenv("MCSM_INSTANCE_ID") + MCSMDeploy(apiUrl, apiKey, daemonId, instanceId).deploy(file("build/libs/FancyBot-1.0.0-all.jar")) } \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000..3931f03 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + kotlin("jvm") version "2.0.10" +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.14") + implementation("com.google.code.gson:gson:2.11.0") +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/buildsrc/Http.kt b/buildSrc/src/main/kotlin/buildsrc/Http.kt new file mode 100644 index 0000000..3fb2479 --- /dev/null +++ b/buildSrc/src/main/kotlin/buildsrc/Http.kt @@ -0,0 +1,123 @@ +/* + * Copyright © 2024 RTAkland + * Author: RTAkland + * Date: 2024/9/14 + */ + + +package buildsrc + +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.MultipartBody +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.File + + +object Http { + + val jsonMediaType = "application/json; charset=utf-8".toMediaType() + val jsonHeader = mapOf( + "Content-Type" to "application/json; charset=utf-8", + "Accept" to "application/json" + ) + val okHttpClient = OkHttpClient() + + fun buildParams(url: String, params: Map?): String { + val paramsUrl = StringBuilder("$url?") + params?.let { + it.forEach { (key, value) -> + paramsUrl.append("$key=$value&") + } + paramsUrl.setLength(paramsUrl.length - 1) + } + return if (params != null) paramsUrl.toString() else url + } + + fun addHeaders(request: Request.Builder, headers: Map?): Request.Builder { + headers?.let { + it.forEach { (key, value) -> + request.addHeader(key, value) + } + } + return request + } + + @JvmOverloads + fun delete( + url: String, + jsonBody: String? = null, + params: Map? = null, + headers: Map? = null + ): String { + val paramsUrl = buildParams(url, params) + val requestBuilder = Request.Builder().url(paramsUrl) + jsonBody?.let { + val body = it.toRequestBody(jsonMediaType) + requestBuilder.delete(body) + } ?: requestBuilder.delete() + + val headerRequest = addHeaders(requestBuilder, headers) + return executeRequest(headerRequest.build()) + } + + @JvmOverloads + fun postFile( + url: String, + file: File, + formBody: Map? = null, + headers: Map? = null, + params: Map? = null + ): String { + val multipartBody = MultipartBody.Builder().setType(MultipartBody.FORM) + formBody?.forEach { (key, value) -> + multipartBody.addFormDataPart(key, value.toString()) + } + val fileMediaType = "application/octet-stream".toMediaType() + multipartBody.addFormDataPart("file", file.name, file.asRequestBody(fileMediaType)) + + val paramsUrl = buildParams(url, params) + val request = Request.Builder() + .url(paramsUrl) + .post(multipartBody.build()) + val headerRequest = addHeaders(request, headers) + return executeRequest(headerRequest.build()) + } + + private fun executeRequest(request: Request): String { + return okHttpClient.newCall(request).execute().body.string() + } + + @JvmOverloads + fun post( + url: String, + jsonBody: String, + headers: Map? = null, + params: Map? = null + ): String { + val body = jsonBody.toRequestBody(jsonMediaType) + val paramsUrl = buildParams(url, params) + val request = Request.Builder() + .post(body) + .url(paramsUrl) + this.addHeaders(request, jsonHeader) + val headerRequest = addHeaders(request, headers) + return this.executeRequest(headerRequest.build()) + } + + @JvmOverloads + fun get( + url: String, + params: Map? = null, + headers: Map? = null + ): String { + val paramsUrl = buildParams(url, params) + val request = Request.Builder() + .url(paramsUrl) + .get() + val headerRequest = addHeaders(request, headers) + return this.executeRequest(headerRequest.build()) + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/buildsrc/Json.kt b/buildSrc/src/main/kotlin/buildsrc/Json.kt new file mode 100644 index 0000000..9193714 --- /dev/null +++ b/buildSrc/src/main/kotlin/buildsrc/Json.kt @@ -0,0 +1,30 @@ +/* + * Copyright © 2024 RTAkland + * Author: RTAkland + * Date: 2024/9/14 + */ + + +package buildsrc + +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.reflect.TypeToken + +val gson: Gson = GsonBuilder() + .setPrettyPrinting() + .disableHtmlEscaping() + .create() + + +fun Any.toJson(): String { + return gson.toJson(this) +} + +inline fun String.fromJson(): T { + return gson.fromJson(this, T::class.java) +} + +inline fun String.fromArrayJson(): T { + return gson.fromJson(this, object : TypeToken() {}.type) +} diff --git a/buildSrc/src/main/kotlin/buildsrc/MCSMDeploy.kt b/buildSrc/src/main/kotlin/buildsrc/MCSMDeploy.kt new file mode 100644 index 0000000..b9f03b9 --- /dev/null +++ b/buildSrc/src/main/kotlin/buildsrc/MCSMDeploy.kt @@ -0,0 +1,66 @@ +/* + * Copyright © 2024 RTAkland + * Author: RTAkland + * Date: 2024/9/14 + */ + + +package buildsrc + +import java.io.File + +class MCSMDeploy( + private val mcsmApiUrl: String, + private val apiKey: String, + private val daemonId: String, + private val instanceId: String +) { + + private fun deleteLastTimeJar() { + Http.delete( + "$mcsmApiUrl/api/files", + DeleteTarget(listOf("/FancyBot-1.0.0-all.jar")).toJson(), + mapOf( + "daemonId" to daemonId, + "uuid" to instanceId, + "apikey" to apiKey + ) + ) + } + + private fun getUploadConfig(): Pair { + val result = Http.post( + "$mcsmApiUrl/api/files/upload?apikey=$apiKey&upload_dir=/&daemonId=$daemonId&uuid=$instanceId", "" + ) + val config = result.fromJson().data + return config.password to config.addr + } + + private fun uploadFile(file: File, password: String, addr: String) { + Http.postFile("${addr.replace("wss", "https")}/upload/$password", file) + } + + private fun restartInstance() { + Http.get("$mcsmApiUrl/api/protected_instance/restart?apikey=$apiKey&daemonId=$daemonId&uuid=$instanceId") + } + + fun deploy(file: File) { + this.deleteLastTimeJar() + val config = this.getUploadConfig() + this.uploadFile(file, config.first, config.second) + this.restartInstance() + } + + data class DeleteTarget( + val targets: List + ) + + data class UploadConfig( + val data: Data + ) { + data class Data( + val password: String, + val addr: String + ) + } +} \ No newline at end of file