add HTTPAPI
This commit is contained in:
1 parent
dddabe30cc
commit
ee1c92e36f
4 files changed
+113
-4
No files matched your search
+2
-1
@@ -30,7 +30,8 @@ dependencies {
|
|||||||
// Fabric API. This is technically optional, but you probably want it anyway.
|
// Fabric API. This is technically optional, but you probably want it anyway.
|
||||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||||
shadow('com.maxmind.geoip2:geoip2:4.2.0')
|
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') {
|
shadow('com.google.inject:guice:7.0.0') {
|
||||||
exclude group: 'com.google.guava', module: 'guava'
|
exclude group: 'com.google.guava', module: 'guava'
|
||||||
exclude group: 'com.google.guava', module: 'listenablefuture'
|
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;
|
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 com.maxmind.geoip2.exception.GeoIp2Exception;
|
||||||
import io.ib67.sfcraft.SFCraft;
|
import io.ib67.sfcraft.SFCraft;
|
||||||
import io.ib67.sfcraft.config.SFConfig;
|
import io.ib67.sfcraft.config.SFConfig;
|
||||||
@@ -29,8 +32,10 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class Helper {
|
public class Helper {
|
||||||
|
private static final Pattern ILLEGAL_CHARACTERS = Pattern.compile("[./\\\\#%!@&*]");
|
||||||
public static final char COLOR = '§';
|
public static final char COLOR = '§';
|
||||||
|
|
||||||
public static boolean canBack(ServerPlayerEntity player) {
|
public static boolean canBack(ServerPlayerEntity player) {
|
||||||
@@ -43,6 +48,15 @@ public class Helper {
|
|||||||
return nearby != null;
|
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) {
|
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 pos = new BlockPos(x, y, z);
|
||||||
var stand = world.getBlockState(pos);
|
var stand = world.getBlockState(pos);
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ public class LitematicConverter implements AutoCloseable {
|
|||||||
public static final String NBT_LITEMATICA_ROOT = "";
|
public static final String NBT_LITEMATICA_ROOT = "";
|
||||||
private final InputStream input;
|
private final InputStream input;
|
||||||
private final NbtSizeTracker sizeTracker;
|
private final NbtSizeTracker sizeTracker;
|
||||||
private NbtCompound root;
|
|
||||||
|
|
||||||
public LitematicConverter(InputStream input, NbtSizeTracker sizeTracker) {
|
public LitematicConverter(InputStream input, NbtSizeTracker sizeTracker) {
|
||||||
this.input = input;
|
this.input = input;
|
||||||
@@ -31,8 +30,7 @@ public class LitematicConverter implements AutoCloseable {
|
|||||||
public void read(
|
public void read(
|
||||||
BiConsumer<String, NbtCompound> schematicOutput
|
BiConsumer<String, NbtCompound> schematicOutput
|
||||||
) {
|
) {
|
||||||
if (root != null) throw new IllegalStateException("Litematica file is already read");
|
var root = NbtIo.readCompressed(input, sizeTracker).getCompound(NBT_LITEMATICA_ROOT);
|
||||||
root = NbtIo.readCompressed(input, sizeTracker).getCompound(NBT_LITEMATICA_ROOT);
|
|
||||||
var dataVersion = root.getInt("MinecraftDataVersion");
|
var dataVersion = root.getInt("MinecraftDataVersion");
|
||||||
var regionsNbt = root.getCompound("Regions");
|
var regionsNbt = root.getCompound("Regions");
|
||||||
var i = 0;
|
var i = 0;
|
||||||
|
|||||||
Reference in new issue
Block a user