Skip to content

Commit d25d7e0

Browse files
committed
jspm integration
1 parent 42aae03 commit d25d7e0

File tree

5 files changed

+307
-57
lines changed

5 files changed

+307
-57
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package de.blazemcworld.jsscripts;
2+
3+
import com.google.gson.JsonArray;
4+
import com.google.gson.JsonElement;
5+
import com.google.gson.JsonObject;
6+
import com.google.gson.JsonParser;
7+
import net.minecraft.text.Text;
8+
import net.minecraft.util.Formatting;
9+
10+
import java.io.File;
11+
import java.net.URI;
12+
import java.net.http.HttpClient;
13+
import java.net.http.HttpRequest;
14+
import java.net.http.HttpResponse;
15+
import java.nio.charset.StandardCharsets;
16+
import java.nio.file.Files;
17+
import java.nio.file.Path;
18+
import java.security.MessageDigest;
19+
import java.security.SecureRandom;
20+
import java.util.Random;
21+
22+
public class JSPM {
23+
24+
private static final String HOST = "https://backend-1-a2537223.deta.app";
25+
private static String token = "";
26+
private static long expires = 0;
27+
28+
public static void upload(String name, byte[] zip) throws Exception {
29+
HttpClient client = HttpClient.newHttpClient();
30+
31+
HttpRequest req = HttpRequest.newBuilder()
32+
.uri(URI.create(HOST + "/pkg/" + name))
33+
.header("Authorization", getToken())
34+
.header("Content-Type", "application/octet-stream")
35+
.POST(HttpRequest.BodyPublishers.ofByteArray(zip))
36+
.build();
37+
38+
JsonObject res = JsonParser.parseString(client.send(req, HttpResponse.BodyHandlers.ofString()).body()).getAsJsonObject();
39+
if (!res.get("success").getAsBoolean()) {
40+
throw new Exception(res.get("error").getAsString());
41+
}
42+
}
43+
44+
private static String getToken() throws Exception {
45+
if (expires > System.currentTimeMillis()) {
46+
return token;
47+
}
48+
49+
HttpClient client = HttpClient.newHttpClient();
50+
51+
HttpRequest req = HttpRequest.newBuilder()
52+
.uri(URI.create(HOST + "/auth/getnonce/" + JsScripts.MC.getSession().getProfile().getId().toString()))
53+
.build();
54+
55+
JsonObject nonceRes = JsonParser.parseString(client.send(req, HttpResponse.BodyHandlers.ofString()).body()).getAsJsonObject();
56+
if (!nonceRes.get("success").getAsBoolean()) {
57+
throw new Exception(nonceRes.get("error").getAsString());
58+
}
59+
String serverNonce = nonceRes.get("nonce").getAsString();
60+
61+
StringBuilder clientNonce = new StringBuilder();
62+
63+
Random rng = new SecureRandom();
64+
String chars = "0123456789abcdef";
65+
for (int i = 0; i < 32; i++) {
66+
clientNonce.append(chars.charAt(rng.nextInt(16)));
67+
}
68+
69+
MessageDigest digester = MessageDigest.getInstance("SHA-256");
70+
digester.update((serverNonce + "+" + clientNonce).getBytes(StandardCharsets.UTF_8));
71+
byte[] hashBytes = digester.digest();
72+
StringBuilder hash = new StringBuilder();
73+
for (byte b : hashBytes) {
74+
hash.append(String.format("%02x", b));
75+
}
76+
77+
JsScripts.MC.getSessionService().joinServer(
78+
JsScripts.MC.getSession().getProfile(),
79+
JsScripts.MC.getSession().getAccessToken(),
80+
hash.substring(0, 40)
81+
);
82+
83+
req = HttpRequest.newBuilder()
84+
.uri(URI.create(HOST + "/auth/puttoken/" + JsScripts.MC.getSession().getProfile().getId().toString()))
85+
.header("Content-Type", "application/json")
86+
.POST(HttpRequest.BodyPublishers.ofString("""
87+
{
88+
"nonce": "%s"
89+
}
90+
""".formatted(clientNonce)))
91+
.build();
92+
93+
JsonObject tokenRes = JsonParser.parseString(client.send(req, HttpResponse.BodyHandlers.ofString()).body()).getAsJsonObject();
94+
if (!tokenRes.get("success").getAsBoolean()) {
95+
throw new Exception(nonceRes.get("error").getAsString());
96+
}
97+
token = tokenRes.get("token").getAsString();
98+
expires = System.currentTimeMillis() + 50 * 60 * 1000;
99+
100+
return token;
101+
}
102+
103+
public static void download(String name) throws Exception {
104+
downloadDir(name);
105+
}
106+
107+
private static void downloadDir(String path) throws Exception {
108+
HttpClient client = HttpClient.newHttpClient();
109+
110+
HttpRequest req = HttpRequest.newBuilder()
111+
.uri(URI.create("https://api.github.com/repos/McJsScripts/JSPMRegistry/contents/packages/" + path))
112+
.build();
113+
114+
JsonArray res = JsonParser.parseString(client.send(req, HttpResponse.BodyHandlers.ofString()).body()).getAsJsonArray();
115+
116+
for (JsonElement f : res) {
117+
if (f.getAsJsonObject().get("type").getAsString().equals("dir")) {
118+
downloadDir(f.getAsJsonObject().get("path").getAsString());
119+
}
120+
if (f.getAsJsonObject().get("type").getAsString().equals("file")) {
121+
req = HttpRequest.newBuilder()
122+
.uri(URI.create(f.getAsJsonObject().get("download_url").getAsString()))
123+
.build();
124+
125+
String filePath = f.getAsJsonObject().get("path").getAsString().replaceFirst("packages/", "");
126+
JsScripts.displayChat(Text.literal("Downloading " + filePath).formatted(Formatting.AQUA));
127+
Path p = ScriptManager.modDir.resolve("scripts").resolve(filePath.replace('/', File.separatorChar));
128+
Files.createDirectories(p.getParent());
129+
Files.write(p, client.send(req, HttpResponse.BodyHandlers.ofByteArray()).body());
130+
}
131+
}
132+
}
133+
134+
public static boolean has(String name) throws Exception {
135+
HttpClient client = HttpClient.newHttpClient();
136+
137+
HttpRequest req = HttpRequest.newBuilder()
138+
.uri(URI.create("https://api.github.com/repos/McJsScripts/JSPMRegistry/contents/packages/" + name))
139+
.build();
140+
141+
return client.send(req, HttpResponse.BodyHandlers.discarding()).statusCode() != 404;
142+
}
143+
}

src/main/java/de/blazemcworld/jsscripts/JsScriptsCmd.java

Lines changed: 141 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@
33
import com.google.gson.*;
44
import com.mojang.brigadier.arguments.StringArgumentType;
55
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
6+
import net.minecraft.MinecraftVersion;
67
import net.minecraft.text.ClickEvent;
78
import net.minecraft.text.Text;
89
import net.minecraft.text.TextColor;
910
import net.minecraft.util.Formatting;
1011

12+
import java.io.ByteArrayOutputStream;
1113
import java.io.File;
1214
import java.nio.file.Files;
1315
import java.nio.file.Path;
1416
import java.util.ArrayList;
1517
import java.util.List;
18+
import java.util.stream.Stream;
19+
import java.util.zip.ZipEntry;
20+
import java.util.zip.ZipOutputStream;
1621

1722
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
1823
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
@@ -28,6 +33,8 @@ public void register() {
2833
JsScripts.displayChat(Text.literal("/jsscripts list - List all currently enabled scripts.").formatted(Formatting.AQUA));
2934
JsScripts.displayChat(Text.literal("/jsscripts enable - Add a script to the auto-enable list.").formatted(Formatting.AQUA));
3035
JsScripts.displayChat(Text.literal("/jsscripts disable - Remove a script from the auto-enable list.").formatted(Formatting.AQUA));
36+
JsScripts.displayChat(Text.literal("/jsscripts upload - Upload a script to jspm.").formatted(Formatting.AQUA));
37+
JsScripts.displayChat(Text.literal("/jsscripts download - Download a script from jspm.").formatted(Formatting.AQUA));
3138
return 1;
3239
})
3340
.then(literal("reload")
@@ -66,12 +73,34 @@ public void register() {
6673
)
6774
.then(literal("list")
6875
.executes(e -> {
69-
int[] counts = {0, 0}; //total, loaded
76+
int total = 0;
77+
int loaded = 0;
78+
7079
List<Text> messages = new ArrayList<>();
71-
messages.addAll(listScripts(ScriptManager.modDir.resolve("jspm"), "jspm/", counts, true));
72-
messages.addAll(listScripts(ScriptManager.modDir.resolve("scripts"), "local/", counts, true));
7380

74-
JsScripts.displayChat(Text.literal("Scripts (" + counts[1] + " of " + counts[0] + " loaded)").formatted(Formatting.AQUA));
81+
File dir = ScriptManager.modDir.resolve("scripts").toFile();
82+
83+
for (File f : dir.listFiles()) {
84+
boolean isLoaded = ScriptManager.loadedScripts.contains(f.toPath().resolve("index.js").toAbsolutePath().toString());
85+
boolean isDirect = ScriptManager.configData.getAsJsonArray("enabled_scripts").contains(new JsonPrimitive(f.getName()));
86+
87+
total++;
88+
loaded += isLoaded ? 1 : 0;
89+
90+
if (isLoaded) {
91+
if (isDirect) {
92+
messages.add(Text.literal(f.getName() + " - Enabled").formatted(Formatting.GREEN)
93+
.styled(s -> s.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/jsscripts disable " + f.getName()))));
94+
} else {
95+
messages.add(Text.literal(f.getName() + " - Dependency").styled(s -> s.withColor(TextColor.fromRgb(4031824))));
96+
}
97+
} else {
98+
messages.add(Text.literal(f.getName() + " - Disabled").formatted(Formatting.GRAY)
99+
.styled(s -> s.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/jsscripts enable " + f.getName()))));
100+
}
101+
}
102+
103+
JsScripts.displayChat(Text.literal("Scripts (" + loaded + " of " + total + " loaded)").formatted(Formatting.AQUA));
75104

76105
for (Text msg : messages) {
77106
JsScripts.displayChat(msg);
@@ -116,7 +145,6 @@ public void register() {
116145
})
117146
)
118147
)
119-
120148
.then(literal("disable")
121149
.executes((e) -> {
122150
JsScripts.displayChat(Text.literal("Invalid usage! Usage:").formatted(Formatting.AQUA));
@@ -154,39 +182,114 @@ public void register() {
154182
})
155183
)
156184
)
157-
));
158-
}
185+
.then(literal("upload")
186+
.executes((e) -> {
187+
JsScripts.displayChat(Text.literal("Invalid usage! Usage:").formatted(Formatting.AQUA));
188+
JsScripts.displayChat(Text.literal("/jsscripts upload <script>").formatted(Formatting.AQUA));
189+
return 1;
190+
})
191+
.then(argument("script", StringArgumentType.greedyString())
192+
.executes((e) -> {
193+
new Thread(() -> {
194+
try {
195+
String name = e.getArgument("script", String.class);
196+
Path root = ScriptManager.modDir.resolve("scripts").resolve(name);
159197

160-
private List<Text> listScripts(Path p, String prefix, int[] counts, boolean root) {
161-
File f = p.toFile();
162-
List<Text> messages = new ArrayList<>();
163-
164-
if (f.isDirectory()) {
165-
for (File child : f.listFiles()) {
166-
messages.addAll(listScripts(child.toPath(), prefix + (root ? "" : f.getName() + "/"), counts, false));
167-
}
168-
return messages;
169-
}
170-
171-
boolean loaded = ScriptManager.loadedScripts.contains(p.toAbsolutePath().toString());
172-
boolean direct = ScriptManager.configData.getAsJsonArray("enabled_scripts").contains(new JsonPrimitive(prefix + f.getName()));
173-
174-
counts[0]++;
175-
counts[1] += loaded ? 1 : 0;
176-
177-
if (loaded) {
178-
if (direct) {
179-
messages.add(Text.literal(prefix + f.getName() + " - Enabled").formatted(Formatting.GREEN)
180-
.styled(s -> s.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/jsscripts disable " + prefix + f.getName()))));
181-
} else {
182-
messages.add(Text.literal(prefix + f.getName() + " - Dependency").styled(s -> s.withColor(TextColor.fromRgb(4031824))));
183-
}
184-
} else {
185-
messages.add(Text.literal(prefix + f.getName() + " - Disabled").formatted(Formatting.GRAY)
186-
.styled(s -> s.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/jsscripts enable " + prefix + f.getName()))));
187-
}
188-
189-
return messages;
190-
}
198+
if (!root.toFile().exists()) {
199+
JsScripts.displayChat(Text.literal("Unknown Script!").formatted(Formatting.RED));
200+
return;
201+
}
202+
203+
Path configFile = root.resolve("jspm.json");
204+
if (!configFile.toFile().exists()) {
205+
Files.writeString(configFile, """
206+
{
207+
"author": {
208+
"name": "%s",
209+
"uuid": "%s"
210+
},
211+
"version": {
212+
"pkg": "1.0.0",
213+
"minecraft": "%s"
214+
}
215+
}
216+
""".formatted(
217+
JsScripts.MC.getSession().getProfile().getName(),
218+
JsScripts.MC.getSession().getProfile().getId().toString(),
219+
MinecraftVersion.CURRENT.getName()
220+
));
221+
JsScripts.displayChat(Text.literal("Please update the newly created jspm.json file in the script if necessary, then retry.").formatted(Formatting.AQUA));
222+
return;
223+
}
224+
225+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
226+
ZipOutputStream zos = new ZipOutputStream(baos);
227+
228+
try (Stream<Path> paths = Files.walk(root)) {
229+
for (Path p : paths.toList()) {
230+
if (Files.isDirectory(p)) {
231+
continue;
232+
}
233+
ZipEntry zipEntry = new ZipEntry(root.relativize(p).toString());
234+
zos.putNextEntry(zipEntry);
235+
byte[] bytes = Files.readAllBytes(p);
236+
zos.write(bytes, 0, bytes.length);
237+
zos.closeEntry();
238+
}
239+
}
240+
241+
zos.close();
242+
baos.close();
243+
244+
JsScripts.displayChat(Text.literal("Uploading script to JSPM...").formatted(Formatting.AQUA));
245+
JSPM.upload(name, baos.toByteArray());
246+
JsScripts.displayChat(Text.literal("Done.").formatted(Formatting.AQUA));
247+
} catch (Exception err) {
248+
err.printStackTrace();
249+
JsScripts.displayChat(Text.literal("Error (" + err.getMessage() + ")").formatted(Formatting.RED));
250+
}
251+
}).start();
252+
return 1;
253+
})
254+
)
255+
)
256+
.then(literal("download")
257+
.executes((e) -> {
258+
JsScripts.displayChat(Text.literal("Invalid usage! Usage:").formatted(Formatting.AQUA));
259+
JsScripts.displayChat(Text.literal("/jsscripts download <script>").formatted(Formatting.AQUA));
260+
return 1;
261+
})
262+
.then(argument("script", StringArgumentType.greedyString())
263+
.executes((e) -> {
264+
new Thread(() -> {
265+
try {
266+
String name = e.getArgument("script", String.class);
267+
Path root = ScriptManager.modDir.resolve("scripts").resolve(name);
268+
269+
if (!JSPM.has(name)) {
270+
JsScripts.displayChat(Text.literal("Script doesn't exist on JSPM!").formatted(Formatting.RED));
271+
return;
272+
}
191273

274+
if (root.toFile().exists()) {
275+
JsScripts.displayChat(Text.literal("Script already found locally! Delete it to re-download.").formatted(Formatting.RED));
276+
return;
277+
}
278+
279+
JsScripts.displayChat(Text.literal("Downloading script from JSPM...").formatted(Formatting.AQUA));
280+
JSPM.download(name);
281+
JsScripts.displayChat(Text.literal("Done.").formatted(Formatting.AQUA));
282+
JsScripts.displayChat(Text.literal("Click to reload now.").formatted(Formatting.AQUA)
283+
.styled(s -> s.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/jsscripts reload"))));
284+
} catch (Exception err) {
285+
err.printStackTrace();
286+
JsScripts.displayChat(Text.literal("Error (" + err.getMessage() + ")").formatted(Formatting.RED));
287+
}
288+
}).start();
289+
return 1;
290+
})
291+
)
292+
)
293+
));
294+
}
192295
}

0 commit comments

Comments
 (0)