add HTTPAPI

This commit is contained in:
iceBear67 committed 2025-01-06 20:31:00 +08:00
1 parent dddabe30cc
commit ee1c92e36f
4 files changed
+113 -4

No files matched your search

+2 -1
View File
@@ -30,7 +30,8 @@ dependencies {
// Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
shadow('com.maxmind.geoip2:geoip2:4.2.0')
implementation("io.javalin:javalin:6.4.0")
implementation "io.javalin:javalin:6.4.0"
implementation 'com.github.houbb:pinyin:0.4.0'
shadow('com.google.inject:guice:7.0.0') {
exclude group: 'com.google.guava', module: 'guava'
exclude group: 'com.google.guava', module: 'listenablefuture'
@@ -0,0 +1,96 @@
package io.ib67.sfcraft.module.supervisor;
import com.google.inject.Inject;
import io.ib67.sfcraft.ServerModule;
import io.ib67.sfcraft.config.SFConfig;
import io.ib67.sfcraft.module.SignatureService;
import io.ib67.sfcraft.util.Helper;
import io.ib67.sfcraft.util.LitematicConverter;
import io.javalin.Javalin;
import io.javalin.http.Context;
import io.javalin.http.UploadedFile;
import io.netty.buffer.Unpooled;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.NbtSizeTracker;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.regex.Pattern;
@Log4j2
public class WebModule extends ServerModule {
private static final int PERMISSION_UPLOAD_SCHEMATICS = 2;
private static final Path SCHEMATIC_DIR = Path.of("config/worldedit/schematics/");
@Inject
SFConfig config;
@Inject
SignatureService signatureService;
@Override
@SneakyThrows
public void onInitialize() {
Files.createDirectories(SCHEMATIC_DIR);
Thread.ofVirtual().name("SFCraft API Web").start(() -> {
Javalin.create(cfg -> cfg.useVirtualThreads = true)
.post("/api/schematics", this::uploadSchematic)
.start(config.httpPort);
});
}
private void uploadSchematic(@NotNull Context context) {
if (context.contentLength() > config.maxSchematicSize) {
context.result("Content is too large!");
return;
}
var signRaw = Base64.getUrlDecoder().decode(context.pathParam("sign"));
var verifiedSign = signatureService.readSignature(Unpooled.wrappedBuffer(signRaw));
if ((verifiedSign.permission() & PERMISSION_UPLOAD_SCHEMATICS) == 0) {
context.result("Permission denied!");
return;
}
var files = context.uploadedFileMap();
files.forEach((k, v) -> handleUploadSchematic(context, k, v));
}
@SneakyThrows
private void handleUploadSchematic(@NotNull Context context, String fileName, List<UploadedFile> v) {
if (v.size() != 1) {
return;
}
var file = v.getFirst();
if (file.size() > config.maxSchematicSize) {
context.result("Content is too large!");
return;
}
fileName = Helper.cleanFileName(fileName);
if (fileName.endsWith(".schematic")) {
Files.write(SCHEMATIC_DIR.resolve(fileName), file.content().readAllBytes());
file.content().close();
log.info("Saved "+fileName+" as a schematic.");
context.result("Success!");
} else if (fileName.endsWith(".litematic")) {
log.error("Handling new {}", fileName);
var baseFileName = fileName.substring(0, fileName.length() - 10);
new LitematicConverter(
file.content(),
new NbtSizeTracker(config.maxSchematicSize, 16)
).read((name, nbt) -> {
name = Helper.cleanFileName(baseFileName + "-" + name + ".schematic");
try {
NbtIo.writeCompressed(nbt, SCHEMATIC_DIR.resolve(name));
log.info("Schematic" + name+" has been saved!");
} catch (IOException e) {
log.error("Error occurred when serializing .schematic from .litematic.", e);
}
});
}
}
}
@@ -1,5 +1,8 @@
package io.ib67.sfcraft.util;
import com.github.houbb.pinyin.api.impl.Pinyin;
import com.github.houbb.pinyin.constant.enums.PinyinStyleEnum;
import com.github.houbb.pinyin.util.PinyinHelper;
import com.maxmind.geoip2.exception.GeoIp2Exception;
import io.ib67.sfcraft.SFCraft;
import io.ib67.sfcraft.config.SFConfig;
@@ -29,8 +32,10 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.UUID;
import java.util.regex.Pattern;
public class Helper {
private static final Pattern ILLEGAL_CHARACTERS = Pattern.compile("[./\\\\#%!@&*]");
public static final char COLOR = '§';
public static boolean canBack(ServerPlayerEntity player) {
@@ -43,6 +48,15 @@ public class Helper {
return nearby != null;
}
public static String cleanFileName(String filename) {
var name = PinyinHelper.toPinyin(filename, PinyinStyleEnum.NORMAL,"_");
name = ILLEGAL_CHARACTERS.matcher(name).replaceAll("");
if(name.length() > 32){
name = name.substring(0,32);
}
return name;
}
public static boolean teleportSafely(ServerPlayerEntity player, ServerWorld world, int x, int y, int z, float yaw, float pitch) {
var pos = new BlockPos(x, y, z);
var stand = world.getBlockState(pos);
@@ -20,7 +20,6 @@ public class LitematicConverter implements AutoCloseable {
public static final String NBT_LITEMATICA_ROOT = "";
private final InputStream input;
private final NbtSizeTracker sizeTracker;
private NbtCompound root;
public LitematicConverter(InputStream input, NbtSizeTracker sizeTracker) {
this.input = input;
@@ -31,8 +30,7 @@ public class LitematicConverter implements AutoCloseable {
public void read(
BiConsumer<String, NbtCompound> schematicOutput
) {
if (root != null) throw new IllegalStateException("Litematica file is already read");
root = NbtIo.readCompressed(input, sizeTracker).getCompound(NBT_LITEMATICA_ROOT);
var root = NbtIo.readCompressed(input, sizeTracker).getCompound(NBT_LITEMATICA_ROOT);
var dataVersion = root.getInt("MinecraftDataVersion");
var regionsNbt = root.getCompound("Regions");
var i = 0;