From a42db661fcdf1926f391e268d7337351382eafa8 Mon Sep 17 00:00:00 2001 From: Kiber2009 <92222946+Kiber2009@users.noreply.github.com> Date: Thu, 16 Jul 2026 22:02:03 +0300 Subject: [PATCH 1/3] Add `UploadProvider` interface --- .../plugin/contentapi/uploading/UploadProvider.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/io/github/kiber2009/plugin/contentapi/uploading/UploadProvider.java diff --git a/src/main/java/io/github/kiber2009/plugin/contentapi/uploading/UploadProvider.java b/src/main/java/io/github/kiber2009/plugin/contentapi/uploading/UploadProvider.java new file mode 100644 index 0000000..e1a6926 --- /dev/null +++ b/src/main/java/io/github/kiber2009/plugin/contentapi/uploading/UploadProvider.java @@ -0,0 +1,7 @@ +package io.github.kiber2009.plugin.contentapi.uploading; + +import org.jspecify.annotations.NonNull; + +public interface UploadProvider { + @NonNull String uploadBytes(final byte @NonNull [] bytes); +} From 0b5e00282ddc158ef107d496784e970cffdf135e Mon Sep 17 00:00:00 2001 From: Kiber2009 <92222946+Kiber2009@users.noreply.github.com> Date: Thu, 16 Jul 2026 22:03:00 +0300 Subject: [PATCH 2/3] Add `LobfileUploadProvider` for "lobfile.com" --- .../plugin/contentapi/ContentApiPlugin.java | 6 ++ .../uploading/LobfileUploadProvider.java | 69 +++++++++++++++++++ .../plugin/contentapi/utils/HttpUtils.java | 63 +++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 src/main/java/io/github/kiber2009/plugin/contentapi/uploading/LobfileUploadProvider.java create mode 100644 src/main/java/io/github/kiber2009/plugin/contentapi/utils/HttpUtils.java diff --git a/src/main/java/io/github/kiber2009/plugin/contentapi/ContentApiPlugin.java b/src/main/java/io/github/kiber2009/plugin/contentapi/ContentApiPlugin.java index 9625932..39e65ed 100644 --- a/src/main/java/io/github/kiber2009/plugin/contentapi/ContentApiPlugin.java +++ b/src/main/java/io/github/kiber2009/plugin/contentapi/ContentApiPlugin.java @@ -1,12 +1,18 @@ package io.github.kiber2009.plugin.contentapi; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import io.github.kiber2009.plugin.contentapi.commands.GiveCommand; +import io.github.kiber2009.plugin.contentapi.uploading.LobfileUploadProvider; import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import org.bukkit.plugin.java.JavaPlugin; import org.jspecify.annotations.NonNull; public final class ContentApiPlugin extends JavaPlugin { + public static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(LobfileUploadProvider.Response.class, LobfileUploadProvider.Response.Deserializer.INSTANCE) + .create(); private static ContentApiPlugin instance; diff --git a/src/main/java/io/github/kiber2009/plugin/contentapi/uploading/LobfileUploadProvider.java b/src/main/java/io/github/kiber2009/plugin/contentapi/uploading/LobfileUploadProvider.java new file mode 100644 index 0000000..0b70f3a --- /dev/null +++ b/src/main/java/io/github/kiber2009/plugin/contentapi/uploading/LobfileUploadProvider.java @@ -0,0 +1,69 @@ +package io.github.kiber2009.plugin.contentapi.uploading; + +import com.google.gson.*; +import io.github.kiber2009.plugin.contentapi.ContentApiPlugin; +import io.github.kiber2009.plugin.contentapi.utils.HttpUtils; +import org.jspecify.annotations.NonNull; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.UUID; + +public class LobfileUploadProvider implements UploadProvider { + private static final URI UPLOAD_URI = URI.create("https://lobfile.com/api/v3/upload"); + private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient(); + + private final String apiKey; + + public LobfileUploadProvider(final String apiKey) { + this.apiKey = apiKey; + } + + @Override + public @NonNull String uploadBytes(final byte @NonNull [] bytes) { + final String boundary = UUID.randomUUID().toString(); + try { + final HttpRequest request = HttpRequest.newBuilder() + .uri(UPLOAD_URI) + .header("X-API-Key", apiKey) + .header("Content-Type", "multipart/form-data; boundary=" + boundary) + .POST(HttpRequest.BodyPublishers.ofByteArray(HttpUtils.createMultipartBody(boundary, + new HttpUtils.MultipartEntry("file", bytes)))) + .build(); + final HttpResponse httpResponse = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + final Response response = ContentApiPlugin.GSON.fromJson(httpResponse.body(), Response.class); + if (response.success) + return response.url; + else + throw new RuntimeException("Error: " + response.error); + } catch (final IOException | InterruptedException e) { + throw new RuntimeException(e); + } + } + + public record Response(boolean success, String url, String error) { + public static final class Deserializer implements JsonDeserializer { + public static final Deserializer INSTANCE = new Deserializer(); + + private Deserializer() { + } + + @Override + public Response deserialize(final JsonElement json, final Type typeOfT, + final JsonDeserializationContext context) throws JsonParseException { + final JsonObject object = json.getAsJsonObject(); + + final JsonElement url = object.get("url"); + final JsonElement error = object.get("error"); + + return new Response(object.get("success").getAsBoolean(), + url != null ? url.getAsString() : null, + error != null ? error.getAsString() : null); + } + } + } +} diff --git a/src/main/java/io/github/kiber2009/plugin/contentapi/utils/HttpUtils.java b/src/main/java/io/github/kiber2009/plugin/contentapi/utils/HttpUtils.java new file mode 100644 index 0000000..5403ecc --- /dev/null +++ b/src/main/java/io/github/kiber2009/plugin/contentapi/utils/HttpUtils.java @@ -0,0 +1,63 @@ +package io.github.kiber2009.plugin.contentapi.utils; + +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public final class HttpUtils { + private HttpUtils() { + } + + @Contract(pure = true) + public static byte @NonNull [] createMultipartBody(final @NonNull String boundary, + final @NonNull MultipartEntry @NonNull ... entries) throws IOException { + final ByteArrayOutputStream builder = new ByteArrayOutputStream(); + final byte[] separator = ("--" + boundary + "\r\n").getBytes(StandardCharsets.UTF_8); + + builder.write(separator); + + for (final MultipartEntry entry : entries) { + builder.write(entry.getBytes()); + builder.write(separator); + } + + builder.write("--\r\n".getBytes(StandardCharsets.UTF_8)); + + return builder.toByteArray(); + } + + public record MultipartEntry(@Contract(pure = true) @NonNull String name, + @Contract(pure = true) byte @NonNull [] value, + @Contract(pure = true) @Nullable String fileName, + @Contract(pure = true) @Nullable String contentType) { + public MultipartEntry(final @NonNull String name, final byte @NonNull [] value) { + this(name, value, null, null); + } + + public MultipartEntry(final @NonNull String name, final @NonNull String value) { + this(name, value.getBytes(StandardCharsets.UTF_8), null, null); + } + + @Contract(pure = true) + public byte[] getBytes() { + final StringBuilder header = new StringBuilder("Content-Disposition: form-data; name=\"" + name + "\""); + if (fileName != null) + header.append("; filename=\"").append(fileName).append("\""); + header.append("\r\n"); + if (contentType != null) + header.append("Content-Type: ").append(contentType).append("\r\n"); + + final byte[] headerBytes = header.toString().getBytes(StandardCharsets.UTF_8); + + final byte[] res = new byte[headerBytes.length + value.length]; + System.arraycopy(headerBytes, 0, res, 0, headerBytes.length); + System.arraycopy(value, 0, res, headerBytes.length, value.length); + + return res; + } + } +} From e70f6c5094fc94d5d969cb4423bec0f863240f03 Mon Sep 17 00:00:00 2001 From: Kiber2009 <92222946+Kiber2009@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:12:10 +0300 Subject: [PATCH 3/3] Migrate to `org.apache.httpcomponents` --- build.gradle.kts | 6 +- .../uploading/LobfileUploadProvider.java | 47 +++++++------- .../contentapi/uploading/UploadProvider.java | 2 +- .../plugin/contentapi/utils/HttpUtils.java | 63 ------------------- src/main/resources/plugin.yml | 4 +- 5 files changed, 33 insertions(+), 89 deletions(-) delete mode 100644 src/main/java/io/github/kiber2009/plugin/contentapi/utils/HttpUtils.java diff --git a/build.gradle.kts b/build.gradle.kts index e34d0a2..8b469f3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,7 +35,8 @@ tasks { description = "" dependsOn("publishAllPublicationsToMavenCentralRepository") doLast { - val path = "https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/$namespace?publishing_type=automatic" + val path = + "https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/$namespace?publishing_type=automatic" val auth = Base64.getEncoder().encodeToString( "${System.getenv("MAVEN_CENTRAL_USERNAME")}:${System.getenv("MAVEN_CENTRAL_PASSWORD")}" .toByteArray() @@ -94,6 +95,9 @@ repositories { dependencies { @Suppress("VulnerableLibrariesLocal", "RedundantSuppression") compileOnly("io.papermc.paper:paper-api:${mcVersion}.build.+") + + compileOnly("org.apache.httpcomponents:httpclient:4.5.14") + compileOnly("org.apache.httpcomponents:httpmime:4.5.14") } java { diff --git a/src/main/java/io/github/kiber2009/plugin/contentapi/uploading/LobfileUploadProvider.java b/src/main/java/io/github/kiber2009/plugin/contentapi/uploading/LobfileUploadProvider.java index 0b70f3a..2e2d33c 100644 --- a/src/main/java/io/github/kiber2009/plugin/contentapi/uploading/LobfileUploadProvider.java +++ b/src/main/java/io/github/kiber2009/plugin/contentapi/uploading/LobfileUploadProvider.java @@ -2,20 +2,21 @@ import com.google.gson.*; import io.github.kiber2009.plugin.contentapi.ContentApiPlugin; -import io.github.kiber2009.plugin.contentapi.utils.HttpUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; import org.jspecify.annotations.NonNull; import java.io.IOException; import java.lang.reflect.Type; import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.util.UUID; public class LobfileUploadProvider implements UploadProvider { private static final URI UPLOAD_URI = URI.create("https://lobfile.com/api/v3/upload"); - private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient(); private final String apiKey; @@ -24,23 +25,23 @@ public LobfileUploadProvider(final String apiKey) { } @Override - public @NonNull String uploadBytes(final byte @NonNull [] bytes) { - final String boundary = UUID.randomUUID().toString(); - try { - final HttpRequest request = HttpRequest.newBuilder() - .uri(UPLOAD_URI) - .header("X-API-Key", apiKey) - .header("Content-Type", "multipart/form-data; boundary=" + boundary) - .POST(HttpRequest.BodyPublishers.ofByteArray(HttpUtils.createMultipartBody(boundary, - new HttpUtils.MultipartEntry("file", bytes)))) - .build(); - final HttpResponse httpResponse = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); - final Response response = ContentApiPlugin.GSON.fromJson(httpResponse.body(), Response.class); - if (response.success) - return response.url; - else - throw new RuntimeException("Error: " + response.error); - } catch (final IOException | InterruptedException e) { + public @NonNull String uploadBytes(final @NonNull String fileName, final byte @NonNull [] bytes) { + try (final CloseableHttpClient client = HttpClients.createDefault()) { + final HttpPost request = new HttpPost(UPLOAD_URI); + request.setHeader("X-API-Key", apiKey); + request.setEntity(MultipartEntityBuilder.create() + .addBinaryBody("file", bytes, ContentType.APPLICATION_OCTET_STREAM, fileName) + .addTextBody("sha256", "john_doe") + .build()); + try (final CloseableHttpResponse httpResponse = client.execute(request)) { + final String responseString = EntityUtils.toString(httpResponse.getEntity()); + final Response response = ContentApiPlugin.GSON.fromJson(responseString, Response.class); + if (response.success) + return response.url; + else + throw new RuntimeException("Error: " + response.error); + } + } catch (final IOException e) { throw new RuntimeException(e); } } diff --git a/src/main/java/io/github/kiber2009/plugin/contentapi/uploading/UploadProvider.java b/src/main/java/io/github/kiber2009/plugin/contentapi/uploading/UploadProvider.java index e1a6926..7d009a0 100644 --- a/src/main/java/io/github/kiber2009/plugin/contentapi/uploading/UploadProvider.java +++ b/src/main/java/io/github/kiber2009/plugin/contentapi/uploading/UploadProvider.java @@ -3,5 +3,5 @@ import org.jspecify.annotations.NonNull; public interface UploadProvider { - @NonNull String uploadBytes(final byte @NonNull [] bytes); + @NonNull String uploadBytes(final @NonNull String fileName, final byte @NonNull [] bytes); } diff --git a/src/main/java/io/github/kiber2009/plugin/contentapi/utils/HttpUtils.java b/src/main/java/io/github/kiber2009/plugin/contentapi/utils/HttpUtils.java deleted file mode 100644 index 5403ecc..0000000 --- a/src/main/java/io/github/kiber2009/plugin/contentapi/utils/HttpUtils.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.github.kiber2009.plugin.contentapi.utils; - -import org.jetbrains.annotations.Contract; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -public final class HttpUtils { - private HttpUtils() { - } - - @Contract(pure = true) - public static byte @NonNull [] createMultipartBody(final @NonNull String boundary, - final @NonNull MultipartEntry @NonNull ... entries) throws IOException { - final ByteArrayOutputStream builder = new ByteArrayOutputStream(); - final byte[] separator = ("--" + boundary + "\r\n").getBytes(StandardCharsets.UTF_8); - - builder.write(separator); - - for (final MultipartEntry entry : entries) { - builder.write(entry.getBytes()); - builder.write(separator); - } - - builder.write("--\r\n".getBytes(StandardCharsets.UTF_8)); - - return builder.toByteArray(); - } - - public record MultipartEntry(@Contract(pure = true) @NonNull String name, - @Contract(pure = true) byte @NonNull [] value, - @Contract(pure = true) @Nullable String fileName, - @Contract(pure = true) @Nullable String contentType) { - public MultipartEntry(final @NonNull String name, final byte @NonNull [] value) { - this(name, value, null, null); - } - - public MultipartEntry(final @NonNull String name, final @NonNull String value) { - this(name, value.getBytes(StandardCharsets.UTF_8), null, null); - } - - @Contract(pure = true) - public byte[] getBytes() { - final StringBuilder header = new StringBuilder("Content-Disposition: form-data; name=\"" + name + "\""); - if (fileName != null) - header.append("; filename=\"").append(fileName).append("\""); - header.append("\r\n"); - if (contentType != null) - header.append("Content-Type: ").append(contentType).append("\r\n"); - - final byte[] headerBytes = header.toString().getBytes(StandardCharsets.UTF_8); - - final byte[] res = new byte[headerBytes.length + value.length]; - System.arraycopy(headerBytes, 0, res, 0, headerBytes.length); - System.arraycopy(value, 0, res, headerBytes.length, value.length); - - return res; - } - } -} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 6e53b32..19ea0ab 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,4 +3,6 @@ version: $version main: io.github.kiber2009.plugin.contentapi.ContentApiPlugin api-version: $mcVersion author: $author -description: $description \ No newline at end of file +description: $description +libraries: + - org.apache.httpcomponents:httpmime:4.5.14 \ No newline at end of file