Skip to content

Commit

Permalink
jspm integration
Browse files Browse the repository at this point in the history
  • Loading branch information
BlazeMCworld committed Jun 25, 2023
1 parent 42aae03 commit d25d7e0
Show file tree
Hide file tree
Showing 5 changed files with 307 additions and 57 deletions.
143 changes: 143 additions & 0 deletions src/main/java/de/blazemcworld/jsscripts/JSPM.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package de.blazemcworld.jsscripts;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;

import java.io.File;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Random;

public class JSPM {

private static final String HOST = "https://backend-1-a2537223.deta.app";
private static String token = "";
private static long expires = 0;

public static void upload(String name, byte[] zip) throws Exception {
HttpClient client = HttpClient.newHttpClient();

HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(HOST + "/pkg/" + name))
.header("Authorization", getToken())
.header("Content-Type", "application/octet-stream")
.POST(HttpRequest.BodyPublishers.ofByteArray(zip))
.build();

JsonObject res = JsonParser.parseString(client.send(req, HttpResponse.BodyHandlers.ofString()).body()).getAsJsonObject();
if (!res.get("success").getAsBoolean()) {
throw new Exception(res.get("error").getAsString());
}
}

private static String getToken() throws Exception {
if (expires > System.currentTimeMillis()) {
return token;
}

HttpClient client = HttpClient.newHttpClient();

HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(HOST + "/auth/getnonce/" + JsScripts.MC.getSession().getProfile().getId().toString()))
.build();

JsonObject nonceRes = JsonParser.parseString(client.send(req, HttpResponse.BodyHandlers.ofString()).body()).getAsJsonObject();
if (!nonceRes.get("success").getAsBoolean()) {
throw new Exception(nonceRes.get("error").getAsString());
}
String serverNonce = nonceRes.get("nonce").getAsString();

StringBuilder clientNonce = new StringBuilder();

Random rng = new SecureRandom();
String chars = "0123456789abcdef";
for (int i = 0; i < 32; i++) {
clientNonce.append(chars.charAt(rng.nextInt(16)));
}

MessageDigest digester = MessageDigest.getInstance("SHA-256");
digester.update((serverNonce + "+" + clientNonce).getBytes(StandardCharsets.UTF_8));
byte[] hashBytes = digester.digest();
StringBuilder hash = new StringBuilder();
for (byte b : hashBytes) {
hash.append(String.format("%02x", b));
}

JsScripts.MC.getSessionService().joinServer(
JsScripts.MC.getSession().getProfile(),
JsScripts.MC.getSession().getAccessToken(),
hash.substring(0, 40)
);

req = HttpRequest.newBuilder()
.uri(URI.create(HOST + "/auth/puttoken/" + JsScripts.MC.getSession().getProfile().getId().toString()))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("""
{
"nonce": "%s"
}
""".formatted(clientNonce)))
.build();

JsonObject tokenRes = JsonParser.parseString(client.send(req, HttpResponse.BodyHandlers.ofString()).body()).getAsJsonObject();
if (!tokenRes.get("success").getAsBoolean()) {
throw new Exception(nonceRes.get("error").getAsString());
}
token = tokenRes.get("token").getAsString();
expires = System.currentTimeMillis() + 50 * 60 * 1000;

return token;
}

public static void download(String name) throws Exception {
downloadDir(name);
}

private static void downloadDir(String path) throws Exception {
HttpClient client = HttpClient.newHttpClient();

HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.github.com/repos/McJsScripts/JSPMRegistry/contents/packages/" + path))
.build();

JsonArray res = JsonParser.parseString(client.send(req, HttpResponse.BodyHandlers.ofString()).body()).getAsJsonArray();

for (JsonElement f : res) {
if (f.getAsJsonObject().get("type").getAsString().equals("dir")) {
downloadDir(f.getAsJsonObject().get("path").getAsString());
}
if (f.getAsJsonObject().get("type").getAsString().equals("file")) {
req = HttpRequest.newBuilder()
.uri(URI.create(f.getAsJsonObject().get("download_url").getAsString()))
.build();

String filePath = f.getAsJsonObject().get("path").getAsString().replaceFirst("packages/", "");
JsScripts.displayChat(Text.literal("Downloading " + filePath).formatted(Formatting.AQUA));
Path p = ScriptManager.modDir.resolve("scripts").resolve(filePath.replace('/', File.separatorChar));
Files.createDirectories(p.getParent());
Files.write(p, client.send(req, HttpResponse.BodyHandlers.ofByteArray()).body());
}
}
}

public static boolean has(String name) throws Exception {
HttpClient client = HttpClient.newHttpClient();

HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.github.com/repos/McJsScripts/JSPMRegistry/contents/packages/" + name))
.build();

return client.send(req, HttpResponse.BodyHandlers.discarding()).statusCode() != 404;
}
}
179 changes: 141 additions & 38 deletions src/main/java/de/blazemcworld/jsscripts/JsScriptsCmd.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@
import com.google.gson.*;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.minecraft.MinecraftVersion;
import net.minecraft.text.ClickEvent;
import net.minecraft.text.Text;
import net.minecraft.text.TextColor;
import net.minecraft.util.Formatting;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
Expand All @@ -28,6 +33,8 @@ public void register() {
JsScripts.displayChat(Text.literal("/jsscripts list - List all currently enabled scripts.").formatted(Formatting.AQUA));
JsScripts.displayChat(Text.literal("/jsscripts enable - Add a script to the auto-enable list.").formatted(Formatting.AQUA));
JsScripts.displayChat(Text.literal("/jsscripts disable - Remove a script from the auto-enable list.").formatted(Formatting.AQUA));
JsScripts.displayChat(Text.literal("/jsscripts upload - Upload a script to jspm.").formatted(Formatting.AQUA));
JsScripts.displayChat(Text.literal("/jsscripts download - Download a script from jspm.").formatted(Formatting.AQUA));
return 1;
})
.then(literal("reload")
Expand Down Expand Up @@ -66,12 +73,34 @@ public void register() {
)
.then(literal("list")
.executes(e -> {
int[] counts = {0, 0}; //total, loaded
int total = 0;
int loaded = 0;

List<Text> messages = new ArrayList<>();
messages.addAll(listScripts(ScriptManager.modDir.resolve("jspm"), "jspm/", counts, true));
messages.addAll(listScripts(ScriptManager.modDir.resolve("scripts"), "local/", counts, true));

JsScripts.displayChat(Text.literal("Scripts (" + counts[1] + " of " + counts[0] + " loaded)").formatted(Formatting.AQUA));
File dir = ScriptManager.modDir.resolve("scripts").toFile();

for (File f : dir.listFiles()) {
boolean isLoaded = ScriptManager.loadedScripts.contains(f.toPath().resolve("index.js").toAbsolutePath().toString());
boolean isDirect = ScriptManager.configData.getAsJsonArray("enabled_scripts").contains(new JsonPrimitive(f.getName()));

total++;
loaded += isLoaded ? 1 : 0;

if (isLoaded) {
if (isDirect) {
messages.add(Text.literal(f.getName() + " - Enabled").formatted(Formatting.GREEN)
.styled(s -> s.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/jsscripts disable " + f.getName()))));
} else {
messages.add(Text.literal(f.getName() + " - Dependency").styled(s -> s.withColor(TextColor.fromRgb(4031824))));
}
} else {
messages.add(Text.literal(f.getName() + " - Disabled").formatted(Formatting.GRAY)
.styled(s -> s.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/jsscripts enable " + f.getName()))));
}
}

JsScripts.displayChat(Text.literal("Scripts (" + loaded + " of " + total + " loaded)").formatted(Formatting.AQUA));

for (Text msg : messages) {
JsScripts.displayChat(msg);
Expand Down Expand Up @@ -116,7 +145,6 @@ public void register() {
})
)
)

.then(literal("disable")
.executes((e) -> {
JsScripts.displayChat(Text.literal("Invalid usage! Usage:").formatted(Formatting.AQUA));
Expand Down Expand Up @@ -154,39 +182,114 @@ public void register() {
})
)
)
));
}
.then(literal("upload")
.executes((e) -> {
JsScripts.displayChat(Text.literal("Invalid usage! Usage:").formatted(Formatting.AQUA));
JsScripts.displayChat(Text.literal("/jsscripts upload <script>").formatted(Formatting.AQUA));
return 1;
})
.then(argument("script", StringArgumentType.greedyString())
.executes((e) -> {
new Thread(() -> {
try {
String name = e.getArgument("script", String.class);
Path root = ScriptManager.modDir.resolve("scripts").resolve(name);

private List<Text> listScripts(Path p, String prefix, int[] counts, boolean root) {
File f = p.toFile();
List<Text> messages = new ArrayList<>();

if (f.isDirectory()) {
for (File child : f.listFiles()) {
messages.addAll(listScripts(child.toPath(), prefix + (root ? "" : f.getName() + "/"), counts, false));
}
return messages;
}

boolean loaded = ScriptManager.loadedScripts.contains(p.toAbsolutePath().toString());
boolean direct = ScriptManager.configData.getAsJsonArray("enabled_scripts").contains(new JsonPrimitive(prefix + f.getName()));

counts[0]++;
counts[1] += loaded ? 1 : 0;

if (loaded) {
if (direct) {
messages.add(Text.literal(prefix + f.getName() + " - Enabled").formatted(Formatting.GREEN)
.styled(s -> s.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/jsscripts disable " + prefix + f.getName()))));
} else {
messages.add(Text.literal(prefix + f.getName() + " - Dependency").styled(s -> s.withColor(TextColor.fromRgb(4031824))));
}
} else {
messages.add(Text.literal(prefix + f.getName() + " - Disabled").formatted(Formatting.GRAY)
.styled(s -> s.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/jsscripts enable " + prefix + f.getName()))));
}

return messages;
}
if (!root.toFile().exists()) {
JsScripts.displayChat(Text.literal("Unknown Script!").formatted(Formatting.RED));
return;
}

Path configFile = root.resolve("jspm.json");
if (!configFile.toFile().exists()) {
Files.writeString(configFile, """
{
"author": {
"name": "%s",
"uuid": "%s"
},
"version": {
"pkg": "1.0.0",
"minecraft": "%s"
}
}
""".formatted(
JsScripts.MC.getSession().getProfile().getName(),
JsScripts.MC.getSession().getProfile().getId().toString(),
MinecraftVersion.CURRENT.getName()
));
JsScripts.displayChat(Text.literal("Please update the newly created jspm.json file in the script if necessary, then retry.").formatted(Formatting.AQUA));
return;
}

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(baos);

try (Stream<Path> paths = Files.walk(root)) {
for (Path p : paths.toList()) {
if (Files.isDirectory(p)) {
continue;
}
ZipEntry zipEntry = new ZipEntry(root.relativize(p).toString());
zos.putNextEntry(zipEntry);
byte[] bytes = Files.readAllBytes(p);
zos.write(bytes, 0, bytes.length);
zos.closeEntry();
}
}

zos.close();
baos.close();

JsScripts.displayChat(Text.literal("Uploading script to JSPM...").formatted(Formatting.AQUA));
JSPM.upload(name, baos.toByteArray());
JsScripts.displayChat(Text.literal("Done.").formatted(Formatting.AQUA));
} catch (Exception err) {
err.printStackTrace();
JsScripts.displayChat(Text.literal("Error (" + err.getMessage() + ")").formatted(Formatting.RED));
}
}).start();
return 1;
})
)
)
.then(literal("download")
.executes((e) -> {
JsScripts.displayChat(Text.literal("Invalid usage! Usage:").formatted(Formatting.AQUA));
JsScripts.displayChat(Text.literal("/jsscripts download <script>").formatted(Formatting.AQUA));
return 1;
})
.then(argument("script", StringArgumentType.greedyString())
.executes((e) -> {
new Thread(() -> {
try {
String name = e.getArgument("script", String.class);
Path root = ScriptManager.modDir.resolve("scripts").resolve(name);

if (!JSPM.has(name)) {
JsScripts.displayChat(Text.literal("Script doesn't exist on JSPM!").formatted(Formatting.RED));
return;
}

if (root.toFile().exists()) {
JsScripts.displayChat(Text.literal("Script already found locally! Delete it to re-download.").formatted(Formatting.RED));
return;
}

JsScripts.displayChat(Text.literal("Downloading script from JSPM...").formatted(Formatting.AQUA));
JSPM.download(name);
JsScripts.displayChat(Text.literal("Done.").formatted(Formatting.AQUA));
JsScripts.displayChat(Text.literal("Click to reload now.").formatted(Formatting.AQUA)
.styled(s -> s.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/jsscripts reload"))));
} catch (Exception err) {
err.printStackTrace();
JsScripts.displayChat(Text.literal("Error (" + err.getMessage() + ")").formatted(Formatting.RED));
}
}).start();
return 1;
})
)
)
));
}
}
Loading

1 comment on commit d25d7e0

@UserUNP
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

incredible

Please sign in to comment.