9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2026-01-06 15:52:03 +00:00
This commit is contained in:
XiaoMoMi
2025-04-17 23:13:39 +08:00
parent d629245f63
commit a66241494e
4 changed files with 183 additions and 12 deletions

View File

@@ -1,5 +1,7 @@
package net.momirealms.craftengine.core.pack.host;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import java.nio.file.Path;
import java.util.List;
import java.util.UUID;
@@ -10,4 +12,8 @@ public interface ResourcePackHost {
CompletableFuture<List<ResourcePackDownloadData>> requestResourcePackDownloadLink(UUID player);
CompletableFuture<Void> upload(Path resourcePackPath);
static Path customPackPath(String path) {
return path.startsWith(".") ? CraftEngine.instance().dataFolderPath().resolve(path) : Path.of(path);
}
}

View File

@@ -0,0 +1,170 @@
package net.momirealms.craftengine.core.pack.host.impl;
import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData;
import net.momirealms.craftengine.core.pack.host.ResourcePackHost;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
public class LobFileHost implements ResourcePackHost {
private Path forcedPackPath;
private String apiKey;
private String url;
private String sha1;
private UUID uuid;
public LobFileHost(String localFile, String apiKey) {
this.forcedPackPath = localFile == null ? null : ResourcePackHost.customPackPath(localFile);
this.apiKey = apiKey;
}
@Override
public CompletableFuture<List<ResourcePackDownloadData>> requestResourcePackDownloadLink(UUID player) {
if (url == null) return CompletableFuture.completedFuture(Collections.emptyList());
return CompletableFuture.completedFuture(List.of(ResourcePackDownloadData.of(url, uuid, sha1)));
}
@Override
public CompletableFuture<Void> upload(Path resourcePackPath) {
CompletableFuture<Void> future = new CompletableFuture<>();
if (this.forcedPackPath != null) resourcePackPath = forcedPackPath;
try {
// 计算文件的 SHA1 和 SHA256
Map<String, String> hashes = calculateHashes(resourcePackPath);
String sha1Hash = hashes.get("SHA-1");
String sha256Hash = hashes.get("SHA-256");
// 构建 multipart/form-data 请求
HttpClient client = HttpClient.newHttpClient();
String boundary = UUID.randomUUID().toString();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://lobfile.com/api/v3/upload.php"))
.header("X-API-Key", apiKey)
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(buildMultipartBody(resourcePackPath, sha256Hash, boundary))
.build();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenAccept(response -> handleUploadResponse(response, future, sha1Hash))
.exceptionally(ex -> {
future.completeExceptionally(ex);
return null;
});
} catch (IOException | NoSuchAlgorithmException e) {
future.completeExceptionally(e);
}
return future;
}
private Map<String, String> calculateHashes(Path path) throws IOException, NoSuchAlgorithmException {
Map<String, String> hashes = new HashMap<>();
MessageDigest sha1Digest = MessageDigest.getInstance("SHA-1");
MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256");
try (InputStream is = Files.newInputStream(path);
DigestInputStream dis = new DigestInputStream(is, sha1Digest)) {
// 同时计算 SHA-256
DigestInputStream dis2 = new DigestInputStream(dis, sha256Digest);
// 读取文件流触发摘要计算
while (dis2.read() != -1) ;
hashes.put("SHA-1", bytesToHex(sha1Digest.digest()));
hashes.put("SHA-256", bytesToHex(sha256Digest.digest()));
}
return hashes;
}
private HttpRequest.BodyPublisher buildMultipartBody(Path filePath, String sha256Hash, String boundary) throws IOException {
List<byte[]> parts = new ArrayList<>();
String filePartHeader = "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"file\"; filename=\"" + filePath.getFileName() + "\"\r\n" +
"Content-Type: application/octet-stream\r\n\r\n";
parts.add(filePartHeader.getBytes());
parts.add(Files.readAllBytes(filePath));
parts.add("\r\n".getBytes());
String sha256Part = "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"sha_256\"\r\n\r\n" +
sha256Hash + "\r\n";
parts.add(sha256Part.getBytes());
String endBoundary = "--" + boundary + "--\r\n";
parts.add(endBoundary.getBytes());
return HttpRequest.BodyPublishers.ofByteArrays(parts);
}
private void handleUploadResponse(
HttpResponse<String> response,
CompletableFuture<Void> future,
String localSha1
) {
try {
if (response.statusCode() == 200) {
// 解析 JSON 响应
Map<String, Object> json = parseJson(response.body());
if (Boolean.TRUE.equals(json.get("success"))) {
this.url = (String) json.get("url");
this.sha1 = localSha1;
this.uuid = UUID.randomUUID();
future.complete(null);
} else {
future.completeExceptionally(new RuntimeException((String) json.get("error")));
}
} else {
future.completeExceptionally(new RuntimeException("Upload failed: " + response.statusCode()));
}
} catch (Exception e) {
future.completeExceptionally(e);
}
}
// 辅助方法
private String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
@SuppressWarnings("unchecked")
private Map<String, Object> parseJson(String json) {
// 这里使用简单解析(实际项目建议使用 JSON 库)
Map<String, Object> map = new HashMap<>();
json = json.replaceAll("[{}\"]", "");
for (String pair : json.split(",")) {
String[] kv = pair.split(":", 2);
if (kv.length == 2) {
String key = kv[0].trim();
String value = kv[1].trim();
if (key.equals("success")) {
map.put(key, Boolean.parseBoolean(value));
} else {
map.put(key, value);
}
}
}
return map;
}
}

View File

@@ -28,7 +28,7 @@ public class SelfHost implements ResourcePackHost {
CompletableFuture<Void> future = new CompletableFuture<>();
CraftEngine.instance().scheduler().executeAsync(() -> {
try {
SelfHostHttpServer.instance().readResourcePack();
SelfHostHttpServer.instance().readResourcePack(resourcePackPath);
future.complete(null);
} catch (Exception e) {
future.completeExceptionally(e);
@@ -53,9 +53,6 @@ public class SelfHost implements ResourcePackHost {
String protocol = (String) arguments.getOrDefault("protocol", "http");
boolean denyNonMinecraftRequest = (boolean) arguments.getOrDefault("deny-non-minecraft-request", true);
String localFilePath = (String) arguments.get("local-file-path");
if (localFilePath == null) {
throw new IllegalArgumentException("'local-file-path' argument missing for self host");
}
Map<String, Object> rateMap = MiscUtils.castToMap(arguments.get("rate-map"), true);
int maxRequests = 5;
int resetInterval = 20_000;

View File

@@ -6,6 +6,7 @@ import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData;
import net.momirealms.craftengine.core.pack.host.ResourcePackHost;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -65,7 +66,7 @@ public class SelfHostHttpServer {
int resetInternal) {
this.ip = ip;
this.port = port;
this.localFilePath = selfHostPackPath(localFile);
this.localFilePath = localFile == null ? null : ResourcePackHost.customPackPath(localFile);
this.denyNonMinecraft = denyNonMinecraft;
this.protocol = protocol;
this.rateLimit = maxRequests;
@@ -110,18 +111,15 @@ public class SelfHostHttpServer {
);
}
private Path selfHostPackPath(String path) {
return path.startsWith(".") ? CraftEngine.instance().dataFolderPath().resolve(path) : Path.of(path);
}
public String url() {
return protocol + "://" + ip + ":" + port + "/";
}
public void readResourcePack() {
public void readResourcePack(Path path) {
if (this.localFilePath != null) path = this.localFilePath;
try {
if (Files.exists(this.localFilePath)) {
this.resourcePackBytes = Files.readAllBytes(this.localFilePath);
if (Files.exists(path)) {
this.resourcePackBytes = Files.readAllBytes(path);
calculateHash();
} else {
this.resourcePackBytes = null;