Skip to content

Commit 111bc79

Browse files
committed
Bump version to 1.4.1 and enhance avatar URL generation with field-based filtering, modular serialization, and extended webhook support
1 parent d45f40b commit 111bc79

5 files changed

Lines changed: 165 additions & 23 deletions

File tree

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ plugins {
1818
boolean ci = System.getenv("CI") == "true"
1919

2020
group = 'one.armelin'
21-
version = '1.4.0'
21+
version = '1.4.1'
2222

2323
def decomp(fileCollection) {
2424
def jarFile = fileCollection.singleFile

src/main/java/one/armelin/distale/Configuration.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,23 @@ public class Configuration {
3333
@Comment(value = """
3434
Avatar URL for webhook messages; only used if Webhook is enabled.
3535
Available placeholders:
36+
-- User info --
3637
%uuid% | Player UUID
3738
%username% | Player username
39+
-- Skin info --
3840
%json% | Full player skin JSON data
3941
%base64% | Base64 url-safe encoded player skin JSON data
4042
%query% | Query string player skin
43+
- Skin info filters -
44+
type can be any of skin info (json, base64, query)
45+
%type*(field1, field2)% | Extracts only specified fields from the skin info
46+
%type-(field1, field2)% | Removes specified fields from the skin info, keeping the rest of the data
47+
Fields: bodyCharacteristic, underwear, face, eyes, ears, mouth, facialHair,\s
48+
haircut, eyebrows, pants, overpants, undertop, overtop, shoes,\s
49+
headAccessory, faceAccessory, earAccessory, skinFeature, gloves, cape
50+
Examples:
51+
%query-(headAccessory,faceAccessory,earAccessory,cape)% removes accessories from the query string
52+
%query*(face,eyes,mouth,haircut)% keeps only face, eyes, mouth and haircut in the query string
4153
""")
4254
public String avatarUrl = "https://hytale.photo/skin/avatar.png?%query%";
4355

src/main/java/one/armelin/distale/listeners/HytaleEventListener.java

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,8 @@
3030
import one.armelin.distale.utils.*;
3131
import one.armelin.distale.utils.markdown.MarkdownConverter;
3232

33-
import java.nio.charset.StandardCharsets;
34-
import java.util.Base64;
3533
import java.util.Locale;
34+
import java.util.UUID;
3635

3736
/**
3837
* Hytale event listener for DisTale
@@ -212,29 +211,20 @@ public static void onMemoryDiscovered(Player player, NPCMemory npcMemory) {
212211
/**
213212
* Send message via Discord webhook
214213
*
215-
* @param playerName Player's name
214+
* @param playerRef Player
216215
* @param message Message content
217216
*/
218217
public static void sendWebhookMessage(PlayerRef playerRef, String message) {
219218
try {
219+
String playerName = playerRef.getUsername();
220+
UUID playerUuid = playerRef.getUuid();
221+
220222
JsonObject body = new JsonObject();
221-
body.addProperty("username", playerRef.getUsername());
222-
223-
PlayerSkin playerSkin = DisTale.playerSkinCache.get(playerRef.getUuid());
224-
225-
Gson gson = new Gson();
226-
String skinJson = gson.toJson(playerSkin);
227-
String skinBase64 = Base64.getUrlEncoder()
228-
.withoutPadding()
229-
.encodeToString(skinJson.getBytes(StandardCharsets.UTF_8));
230-
String skinQuery = Utils.jsonToQueryString(skinJson);
231-
232-
String avatarUrl = DisTale.config.avatarUrl
233-
.replace("%uuid%", playerRef.getUuid().toString())
234-
.replace("%username%", playerRef.getUsername())
235-
.replace("%json%", skinJson)
236-
.replace("%base64%", skinBase64)
237-
.replace("%query%", skinQuery);
223+
body.addProperty("username", playerName);
224+
225+
PlayerSkin playerSkin = DisTale.playerSkinCache.get(playerUuid);
226+
227+
String avatarUrl = SkinUtils.buildAvatarUrl(DisTale.config.avatarUrl, playerSkin, playerUuid, playerName);
238228

239229
body.addProperty("avatar_url", avatarUrl);
240230

src/main/java/one/armelin/distale/utils/SkinUtils.java

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package one.armelin.distale.utils;
22

3+
import com.google.gson.JsonObject;
34
import com.hypixel.hytale.component.Store;
45
import com.hypixel.hytale.protocol.PlayerSkin;
56
import com.hypixel.hytale.server.core.cosmetics.CosmeticRegistry;
@@ -12,11 +13,15 @@
1213

1314
import javax.annotation.Nullable;
1415
import java.awt.image.BufferedImage;
15-
import java.util.Objects;
16+
import java.nio.charset.StandardCharsets;
17+
import java.util.*;
1618
import java.util.concurrent.CompletableFuture;
1719
import java.util.concurrent.ExecutionException;
20+
import java.util.regex.Matcher;
21+
import java.util.regex.Pattern;
1822

1923
import static one.armelin.distale.utils.ImageUtils.loadImageFromAsset;
24+
import static one.armelin.distale.utils.Utils.jsonToQueryString;
2025

2126
public class SkinUtils {
2227
public static BufferedImage getSkinToneGradient(PlayerSkin playerSkin){
@@ -239,4 +244,139 @@ public static void generateSkin(PlayerRef ref, @Nullable World world){
239244
DisTale.LOGGER.atSevere().withCause(e).log("Error generating skin for player %s", playerName);
240245
}
241246
}
247+
248+
private static final Pattern PATTERN = Pattern.compile("%(json|base64|query)([*-])\\(([^)]+)\\)%|%(json|base64|query)%");
249+
250+
private static final Set<String> ALL_FIELDS = new HashSet<>(Arrays.asList(
251+
"bodyCharacteristic", "underwear", "face", "eyes", "ears", "mouth",
252+
"facialHair", "haircut", "eyebrows", "pants", "overpants", "undertop",
253+
"overtop", "shoes", "headAccessory", "faceAccessory", "earAccessory",
254+
"skinFeature", "gloves", "cape"
255+
));
256+
257+
public static String buildAvatarUrl(String urlTemplate, PlayerSkin skin, UUID uuid, String username) {
258+
StringBuffer result = new StringBuffer();
259+
Matcher matcher = PATTERN.matcher(urlTemplate);
260+
261+
while (matcher.find()) {
262+
String type = matcher.group(1);
263+
String operator = matcher.group(2);
264+
String fieldsStr = matcher.group(3);
265+
266+
Set<String> fields = parseFields(operator, fieldsStr);
267+
String replacement = serialize(skin, type, fields);
268+
269+
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
270+
}
271+
matcher.appendTail(result);
272+
273+
String finalUrl = result.toString();
274+
finalUrl = finalUrl.replace("%uuid%", uuid.toString());
275+
finalUrl = finalUrl.replace("%username%", username);
276+
277+
return finalUrl;
278+
}
279+
280+
private static Set<String> parseFields(String operator, String fieldsStr) {
281+
if (operator == null || fieldsStr == null) {
282+
return new HashSet<>(ALL_FIELDS);
283+
}
284+
285+
String[] fieldArray = fieldsStr.split(",");
286+
Set<String> specifiedFields = new HashSet<>();
287+
for (String field : fieldArray) {
288+
specifiedFields.add(field.trim());
289+
}
290+
291+
if ("*".equals(operator)) {
292+
return specifiedFields;
293+
} else if ("-".equals(operator)) {
294+
Set<String> result = new HashSet<>(ALL_FIELDS);
295+
result.removeAll(specifiedFields);
296+
return result;
297+
}
298+
299+
return new HashSet<>(ALL_FIELDS);
300+
}
301+
302+
private static String serialize(PlayerSkin skin, String type, Set<String> fields) {
303+
JsonObject json = buildJsonObject(skin, fields);
304+
String jsonString = json.toString();
305+
306+
return switch (type.toLowerCase()) {
307+
case "json" -> jsonString;
308+
case "base64" -> Base64.getUrlEncoder()
309+
.withoutPadding()
310+
.encodeToString(jsonString.getBytes(StandardCharsets.UTF_8));
311+
case "query" -> jsonToQueryString(jsonString);
312+
default -> "";
313+
};
314+
}
315+
316+
private static JsonObject buildJsonObject(PlayerSkin skin, Set<String> fields) {
317+
JsonObject json = new JsonObject();
318+
319+
if (fields.contains("bodyCharacteristic") && skin.bodyCharacteristic != null) {
320+
json.addProperty("bodyCharacteristic", skin.bodyCharacteristic);
321+
}
322+
if (fields.contains("underwear") && skin.underwear != null) {
323+
json.addProperty("underwear", skin.underwear);
324+
}
325+
if (fields.contains("face") && skin.face != null) {
326+
json.addProperty("face", skin.face);
327+
}
328+
if (fields.contains("eyes") && skin.eyes != null) {
329+
json.addProperty("eyes", skin.eyes);
330+
}
331+
if (fields.contains("ears") && skin.ears != null) {
332+
json.addProperty("ears", skin.ears);
333+
}
334+
if (fields.contains("mouth") && skin.mouth != null) {
335+
json.addProperty("mouth", skin.mouth);
336+
}
337+
if (fields.contains("facialHair") && skin.facialHair != null) {
338+
json.addProperty("facialHair", skin.facialHair);
339+
}
340+
if (fields.contains("haircut") && skin.haircut != null) {
341+
json.addProperty("haircut", skin.haircut);
342+
}
343+
if (fields.contains("eyebrows") && skin.eyebrows != null) {
344+
json.addProperty("eyebrows", skin.eyebrows);
345+
}
346+
if (fields.contains("pants") && skin.pants != null) {
347+
json.addProperty("pants", skin.pants);
348+
}
349+
if (fields.contains("overpants") && skin.overpants != null) {
350+
json.addProperty("overpants", skin.overpants);
351+
}
352+
if (fields.contains("undertop") && skin.undertop != null) {
353+
json.addProperty("undertop", skin.undertop);
354+
}
355+
if (fields.contains("overtop") && skin.overtop != null) {
356+
json.addProperty("overtop", skin.overtop);
357+
}
358+
if (fields.contains("shoes") && skin.shoes != null) {
359+
json.addProperty("shoes", skin.shoes);
360+
}
361+
if (fields.contains("headAccessory") && skin.headAccessory != null) {
362+
json.addProperty("headAccessory", skin.headAccessory);
363+
}
364+
if (fields.contains("faceAccessory") && skin.faceAccessory != null) {
365+
json.addProperty("faceAccessory", skin.faceAccessory);
366+
}
367+
if (fields.contains("earAccessory") && skin.earAccessory != null) {
368+
json.addProperty("earAccessory", skin.earAccessory);
369+
}
370+
if (fields.contains("skinFeature") && skin.skinFeature != null) {
371+
json.addProperty("skinFeature", skin.skinFeature);
372+
}
373+
if (fields.contains("gloves") && skin.gloves != null) {
374+
json.addProperty("gloves", skin.gloves);
375+
}
376+
if (fields.contains("cape") && skin.cape != null) {
377+
json.addProperty("cape", skin.cape);
378+
}
379+
380+
return json;
381+
}
242382
}

src/main/resources/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"Group": "armelin1",
33
"Name": "DisTale",
4-
"Version": "1.4.0",
4+
"Version": "1.4.1",
55
"Description": "Discord bridge for Hytale servers - Chat integration, announcements, and commands",
66
"Authors": [
77
{

0 commit comments

Comments
 (0)