Initial Commit

This commit is contained in:
Paul Fey 2025-01-12 15:28:49 +01:00
commit 42e01c4b78
12 changed files with 739 additions and 0 deletions

View file

@ -0,0 +1,7 @@
package de.pauljako.cosmeticserver;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public record ClientMapping(UUID uuid, ConcurrentHashMap<String, Object> cosmetics) {
}

View file

@ -0,0 +1,58 @@
package de.pauljako.cosmeticserver;
import de.craftsblock.craftscore.json.Json;
import de.craftsblock.craftscore.json.JsonParser;
import de.craftsblock.craftsnet.addon.Addon;
import de.craftsblock.craftsnet.api.Handler;
import java.io.File;
public class CosmeticServer extends Addon {
private static CosmeticServer instance;
private Json serverCosmetics;
private Thread thread;
@Override
public void onEnable() {
instance = this;
File assets = new File(getDataFolder(), "assets");
assets.mkdirs();
File file = new File(getDataFolder(), "cosmetics.json");
serverCosmetics = JsonParser.parse(file);
routeRegistry().share("/v1/cosmetic/assets", assets);
CosmeticSocket socket = new CosmeticSocket();
routeRegistry().register((Handler) socket);
listenerRegistry().register(socket);
new HeartbeatThread();
}
@Override
public void onDisable() {
try {
thread.interrupt();
thread.join(15);
} catch (Exception e) {
e.printStackTrace();
}
instance = null;
}
public Json getServerCosmetics() {
return serverCosmetics;
}
public static CosmeticServer instance() {
return instance;
}
}

View file

@ -0,0 +1,138 @@
package de.pauljako.cosmeticserver;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import de.craftsblock.craftscore.event.EventHandler;
import de.craftsblock.craftscore.event.ListenerAdapter;
import de.craftsblock.craftscore.json.Json;
import de.craftsblock.craftscore.json.JsonParser;
import de.craftsblock.craftscore.utils.Validator;
import de.craftsblock.craftsnet.api.websocket.*;
import de.craftsblock.craftsnet.api.websocket.annotations.MessageReceiver;
import de.craftsblock.craftsnet.api.websocket.annotations.Socket;
import de.craftsblock.craftsnet.events.sockets.ClientDisconnectEvent;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@Socket("/v1/cosmetic")
public class CosmeticSocket implements SocketHandler, ListenerAdapter {
public static final ConcurrentHashMap<WebSocketClient, ClientMapping> clients = new ConcurrentHashMap<>();
public static final ConcurrentHashMap<UUID, ClientMapping> cosmetics = new ConcurrentHashMap<>();
@MessageReceiver
public void handleMessage(SocketExchange exchange, String message) throws IOException {
WebSocketClient client = exchange.client();
if (!Validator.isJsonValid(message)) {
client.sendMessage(JsonParser.parse("{}")
.set("error", "Send data must be in JSON format")
.asString());
return;
}
Json data = JsonParser.parse(message);
if (data.contains("search")) {
String type = data.getString("search");
String name = data.getString("name");
JsonObject cosmetic = searchServerCosmetic(type, name);
Json result = Json.empty()
.set("id", data.getLong("id"))
.set("search", type)
.set("name", name)
.set("result", false);
if (cosmetic != null) {
result.set("result", cosmetic);
}
exchange.client().sendMessage(result.toString());
return;
}
if (data.contains("cosmetics") && data.contains("uuid")) {
UUID uuid = UUID.fromString(data.getString("uuid"));
ConcurrentHashMap<String, Object> cosmetics = new ConcurrentHashMap<>();
data.get("cosmetics").getAsJsonObject().entrySet().forEach(entry -> cosmetics.put(entry.getKey(), entry.getValue()));
convertToServer(cosmetics);
ClientMapping mapping = new ClientMapping(uuid, cosmetics);
exchange.broadcast(JsonParser.parse("{}")
.set("uuid", uuid.toString())
.set("cosmetics", bakeData(cosmetics))
.asString());
clients.put(client, mapping);
CosmeticSocket.cosmetics.put(uuid, mapping);
}
JsonArray array = new JsonArray();
if (data.contains("data")) {
for (UUID uuid : data.getStringList("data").stream().map(UUID::fromString).toList()) {
if (cosmetics.containsKey(uuid)) {
ClientMapping mapping = cosmetics.get(uuid);
Json target = JsonParser.parse("{}");
target.set("uuid", uuid.toString());
target.set("cosmetics", bakeData(mapping.cosmetics()));
array.add(target.getObject());
}
}
}
client.sendMessage(JsonParser.parse("{}")
.set("data", array)
.asString());
}
private void convertToServer(ConcurrentHashMap<String, Object> cosmetics) {
cosmetics.forEach((type, data) -> {
if (!(data instanceof JsonElement element)) return;
if (!element.isJsonPrimitive() && !element.getAsJsonPrimitive().isString()) return;
String name = element.getAsString();
JsonObject cosmetic = searchServerCosmetic(type, name);
if (cosmetic == null) return;
cosmetics.put(type, cosmetic);
});
}
private JsonObject searchServerCosmetic(String type, String name) {
Json serverCosmetics = CosmeticServer.instance().getServerCosmetics();
if (!serverCosmetics.contains(type + "." + name)) return null;
Json cosmetic = JsonParser.parse(serverCosmetics.get(type + "." + name));
String id = cosmetic.contains("id") ? cosmetic.getString("id") : name;
return (JsonObject) JsonParser.parse("{}")
.set("display", cosmetic.contains("display") ? cosmetic.get("display") : name)
.set("name", name)
.set("delay", cosmetic.contains("delay") ? cosmetic.getInt("delay") : 30)
.set("frames", cosmetic.contains("frames") ? cosmetic.getStringList("frames").size() : 1)
.set("url", "http://cosmetic.airclient.pauljako.de/v1/cosmetic/assets/" + type + "/" + name + "/frame_" + id)
.getObject();
}
private JsonElement bakeData(ConcurrentHashMap<String, Object> elements) {
Json object = JsonParser.parse("{}");
elements.forEach(object::set);
return object.getObject();
}
@EventHandler
public void handleDisconnect(ClientDisconnectEvent event) {
WebSocketClient client = event.getExchange().client();
if (!clients.containsKey(client)) return;
ClientMapping mapping = clients.remove(client);
cosmetics.remove(mapping.uuid());
}
}

View file

@ -0,0 +1,29 @@
package de.pauljako.cosmeticserver;
import de.craftsblock.craftsnet.api.websocket.WebSocketClient;
public class HeartbeatThread implements Runnable{
public HeartbeatThread() {
Thread thread = new Thread(this);
thread.setName("Connection Heartbeat Thread");
thread.start();
Runtime.getRuntime().addShutdownHook(new Thread(thread::interrupt));
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
for (WebSocketClient client : CosmeticSocket.clients.keySet()) {
client.sendMessage("ping");
}
Thread.sleep(15000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

View file

@ -0,0 +1,4 @@
{
"name": "AirServer",
"main": "de.pauljako.cosmeticserver.CosmeticServer"
}