diff --git a/README.md b/README.md index 6498e117..f0faacea 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ The Plugin has some exclusive features, making its use much easier than the CLI. * Automatic updates at regular intervals. * Automatic detection of webservers run by other plugins ([dynmap](https://github.com/webbukkit/dynmap)). -* Support for offline-mode skins via [SkinsRestorer](https://skinsrestorer.net/) (v14.2.2 or later). +* Support for offline-mode skins via [SkinsRestorer](https://skinsrestorer.net/) (v15.0.0 or later). ## Setup @@ -282,6 +282,14 @@ Only since the Java implementation, *MinecraftStats* officially has numbered ver ## Changelog +### 3.31 + +This update uses a newer API of SkinsRestorer and fixes more MOTD issues. + +* Updated the [SkinsRestorer](https://skinsrestorer.net/) API – version 15.0.0 or later is now required. Because of breaking changes in their API, older versions of SkinsRestorer are no longer supported. +* Fixed winners of finished events not displayed correctly. +* Fixed newlines being incorrectly replaced in the MOTD, causing the summary to become unparseable. + ### 3.3.0 This update adds automatic detection of squremap's webserver and fixes bugs in both the plugin and CLI. @@ -307,7 +315,7 @@ This update adds automatic detection of BlueMap's webserver and fixes bugs in th This update adds SkinsRestorer for the plugin and avoids unnecessary Mojang API calls. -* The plugin can now get skins from [SkinsRestorer](https://skinsrestorer.net/) v14.2.2 or later. +* The plugin can now get skins from [SkinsRestorer](https://skinsrestorer.net/) ~~14.2.2~~ 15.0.0 or later. * If a player is detected to be an offline player (e.g., Floodgate players or if the Mojang API gave an empty response), no further attempts at asking the Mojang API will be made. * Minimized the log output of the plugin. diff --git a/build.gradle b/build.gradle index 8640ffa0..ece6a6ef 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ repositories { dependencies { implementation 'org.json:json:20220924' compileOnly 'org.spigotmc:spigot-api:1.13.2-R0.1-SNAPSHOT' - compileOnly 'net.skinsrestorer:skinsrestorer-api:14.2.10' + compileOnly 'net.skinsrestorer:skinsrestorer-api:15.0.0' } task concatJs { diff --git a/src/main/java/de/pdinklag/mcstats/EventWinner.java b/src/main/java/de/pdinklag/mcstats/EventWinner.java new file mode 100644 index 00000000..2fbb867b --- /dev/null +++ b/src/main/java/de/pdinklag/mcstats/EventWinner.java @@ -0,0 +1,48 @@ +package de.pdinklag.mcstats; + +import java.util.function.Function; + +import org.json.JSONArray; +import org.json.JSONObject; + +/** + * Contains summarizing information about the winner of an Event. + */ +public class EventWinner { + /** + * Restores an event winner from a ranking in JSON format. + * + * @param ranking the ranking in JSON format + * @param uuidToPlayer the function that translates a player's UUID to the corresponding Player object + * @return the parsed event winner + */ + public static EventWinner fromJsonRanking(JSONArray ranking, Function uuidToPlayer) { + if (ranking.length() > 0) { + final JSONObject first = ranking.getJSONObject(0); + return new EventWinner(uuidToPlayer.apply(first.getString("uuid")), first); + } else { + return null; + } + } + + private final Player player; + private final JSONObject json; + + private EventWinner(Player player, JSONObject json) { + this.player = player; + this.json = json; + } + + public EventWinner(Ranking.Entry e) { + this.player = e.getPlayer(); + this.json = e.toJSON(); + } + + public Player getPlayer() { + return player; + } + + public JSONObject getJSON() { + return json; + } +} diff --git a/src/main/java/de/pdinklag/mcstats/Updater.java b/src/main/java/de/pdinklag/mcstats/Updater.java index fed04dbd..c6760248 100644 --- a/src/main/java/de/pdinklag/mcstats/Updater.java +++ b/src/main/java/de/pdinklag/mcstats/Updater.java @@ -36,7 +36,8 @@ public abstract class Updater { private static final String DATABASE_PLAYERLIST_ACTIVE_FORMAT = "active%d.json.gz"; private static final String DATABASE_SUMMARY = "summary.json.gz"; - private static final String EVENT_INITIAL_SCORE_FIELD = "initialRanking"; + private static final String EVENT_INITIAL_RANKING_FIELD = "initialRanking"; + private static final String EVENT_RANKING_FIELD = "ranking"; private static final int MINUTES_TO_TICKS = 60 * MinecraftServerUtils.TICKS_PER_SECOND; @@ -441,13 +442,31 @@ public void run() { }); // process events - HashMap.Entry> eventWinners = new HashMap<>(); + HashMap eventWinners = new HashMap<>(); events.values().forEach(event -> { - if (!event.hasEnded(now)) { + final Path eventDataPath = dbEventsPath.resolve(event.getId() + JSON_FILE_EXT); + if (event.hasEnded(now)) { + // event has ended, get winner from JSON and store it into summary + if (Files.exists(eventDataPath)) { + try { + final JSONArray eventRanking = new JSONObject(Files.readString(eventDataPath)) + .getJSONArray(EVENT_RANKING_FIELD); + final EventWinner winner = EventWinner.fromJsonRanking(eventRanking, uuid -> { + return allPlayers.get(uuid); + }); + if (winner != null) { + eventWinners.put(event, winner); + } + } catch (Exception e) { + log.writeError("failed to load winner for ended event " + event.getId(), e); + } + } else { + log.writeLine("event has ended, but data file does not exist: " + event.getId()); + } + } else { + // event has not yet ended, update ranking final Stat linkedStat = awards.get(event.getLinkedStatId()); if (linkedStat != null) { - final Path eventDataPath = dbEventsPath.resolve(event.getId() + JSON_FILE_EXT); - final JSONObject eventData = new JSONObject(); eventData.put("name", event.getId()); eventData.put("title", event.getTitle()); @@ -462,29 +481,29 @@ public void run() { if (Files.exists(eventDataPath)) { try { final JSONObject initialRanking = new JSONObject(Files.readString(eventDataPath)) - .getJSONObject(EVENT_INITIAL_SCORE_FIELD); + .getJSONObject(EVENT_INITIAL_RANKING_FIELD); event.setInitialScores(initialRanking); - eventData.put(EVENT_INITIAL_SCORE_FIELD, initialRanking); + eventData.put(EVENT_INITIAL_RANKING_FIELD, initialRanking); } catch (Exception e) { log.writeError("failed to load initial scores for event " + event.getId(), e); - eventData.put(EVENT_INITIAL_SCORE_FIELD, new JSONObject()); + eventData.put(EVENT_INITIAL_RANKING_FIELD, new JSONObject()); } } else { log.writeLine("event is already running, but no initial scores are available: " + event.getId()); - eventData.put(EVENT_INITIAL_SCORE_FIELD, new JSONObject()); + eventData.put(EVENT_INITIAL_RANKING_FIELD, new JSONObject()); } final Ranking eventRanking = new Ranking(validPlayers, player -> { return new IntValue( player.getStats().get(linkedStat).toInt() - event.getInitialScore(player)); }); - eventData.put("ranking", eventRanking.toJSON()); + eventData.put(EVENT_RANKING_FIELD, eventRanking.toJSON()); // store best for front page List.Entry> rankingEntries = eventRanking.getOrderedEntries(); if (rankingEntries.size() > 0) { - eventWinners.put(event, rankingEntries.get(0)); + eventWinners.put(event, new EventWinner(rankingEntries.get(0))); } } else { // the event has not yet started, update initial scores @@ -496,7 +515,7 @@ public void run() { if (score > 0) initialScores.put(uuid, score); }); - eventData.put(EVENT_INITIAL_SCORE_FIELD, initialScores); + eventData.put(EVENT_INITIAL_RANKING_FIELD, initialScores); } try { @@ -574,8 +593,9 @@ public void run() { if (serverName == null) { // try all data sources for a server.properties file serverName = getServerMotd(); + if (serverName != null) { - serverName = serverName.replace("\\n", "
"); + serverName = serverName.replace("\n", "
"); } if (serverName == null) { @@ -655,9 +675,9 @@ public void run() { eventSummary.put("link", event.getLinkedStatId()); eventSummary.put("active", event.hasStarted(now) && !event.hasEnded(now)); - final Ranking.Entry winner = eventWinners.get(event); + final EventWinner winner = eventWinners.get(event); if (winner != null) { - eventSummary.put("best", winner.toJSON()); + eventSummary.put("best", winner.getJSON()); summaryRelevantPlayers.add(winner.getPlayer()); } diff --git a/src/main/java/de/pdinklag/mcstats/bukkit/BukkitUpdater.java b/src/main/java/de/pdinklag/mcstats/bukkit/BukkitUpdater.java index b52c904f..498cd43e 100644 --- a/src/main/java/de/pdinklag/mcstats/bukkit/BukkitUpdater.java +++ b/src/main/java/de/pdinklag/mcstats/bukkit/BukkitUpdater.java @@ -11,7 +11,7 @@ public class BukkitUpdater extends Updater { private static final String SKINS_RESTORER_PLUGIN_NAME = "SkinsRestorer"; - private static final Version SKINS_RESTORER_MIN_VERSION = new Version(14, 2, 2); + private static final Version SKINS_RESTORER_MIN_VERSION = new Version(15, 0, 0); private final MinecraftStatsPlugin plugin; private boolean isSkinsRestorerAvailable; @@ -44,10 +44,14 @@ public BukkitUpdater(MinecraftStatsPlugin plugin, Config config, LogWriter log) @Override protected PlayerProfileProvider getAuthenticProfileProvider() { if (isSkinsRestorerAvailable) { - return new SkinsRestorerProfileProvider(); - } else { - return super.getAuthenticProfileProvider(); + try { + return new SkinsRestorerProfileProvider(); + } catch (Exception e) { + // trying to retrieve the SkinsRestorer API may fail in certain scenarios + log.writeError("failed to retrieve SkinsRestorer API -- defaulting to Mojang", e); + } } + return super.getAuthenticProfileProvider(); } @Override diff --git a/src/main/java/de/pdinklag/mcstats/bukkit/SkinsRestorerProfileProvider.java b/src/main/java/de/pdinklag/mcstats/bukkit/SkinsRestorerProfileProvider.java index 22cbc279..9cfe92a2 100644 --- a/src/main/java/de/pdinklag/mcstats/bukkit/SkinsRestorerProfileProvider.java +++ b/src/main/java/de/pdinklag/mcstats/bukkit/SkinsRestorerProfileProvider.java @@ -1,45 +1,37 @@ package de.pdinklag.mcstats.bukkit; +import java.util.Optional; +import java.util.UUID; + import de.pdinklag.mcstats.AccountType; import de.pdinklag.mcstats.Player; import de.pdinklag.mcstats.PlayerProfile; import de.pdinklag.mcstats.PlayerProfileProvider; -import net.skinsrestorer.api.SkinsRestorerAPI; -import net.skinsrestorer.api.model.MojangProfileResponse; -import net.skinsrestorer.api.property.IProperty; +import net.skinsrestorer.api.PropertyUtils; +import net.skinsrestorer.api.SkinsRestorer; +import net.skinsrestorer.api.SkinsRestorerProvider; +import net.skinsrestorer.api.property.SkinProperty; public class SkinsRestorerProfileProvider implements PlayerProfileProvider { - private final SkinsRestorerAPI api; + private final SkinsRestorer api; public SkinsRestorerProfileProvider() { - api = SkinsRestorerAPI.getApi(); + api = SkinsRestorerProvider.get(); } @Override public PlayerProfile getPlayerProfile(Player player) { - final PlayerProfile currentProfile = player.getProfile(); - if (currentProfile.hasName()) { - final String skinName = api.getSkinName(currentProfile.getName()); - if (skinName != null) { - final IProperty skinData = api.getSkinData(skinName); - if (skinData != null) { - final String skin = api.getSkinTextureUrlStripped(skinData); - return new PlayerProfile(currentProfile.getName(), skin, System.currentTimeMillis()); - } - } - } - - if (player.getAccountType().maybeMojangAccount()) { - final IProperty skinsRestorerProfile = api.getProfile(player.getUuid()); - if (skinsRestorerProfile != null) { - player.setAccountType(AccountType.MOJANG); - final MojangProfileResponse mojangProfile = api.getSkinProfileData(skinsRestorerProfile); - return new PlayerProfile(mojangProfile.getProfileName(), - mojangProfile.getTextures().getSKIN().getStrippedUrl(), System.currentTimeMillis()); - } else { - player.setAccountType(AccountType.OFFLINE); - } + final Optional skinProperty = api.getPlayerStorage() + .getSkinOfPlayer(UUID.fromString(player.getUuid())); + if (skinProperty.isPresent()) { + player.setAccountType(AccountType.MOJANG); + return new PlayerProfile( + PropertyUtils.getSkinProfileData(skinProperty.get()).getProfileName(), + PropertyUtils.getSkinTextureUrlStripped(skinProperty.get()), + System.currentTimeMillis()); + } else if (player.getAccountType().maybeMojangAccount()) { + player.setAccountType(AccountType.OFFLINE); } - return currentProfile; + return player.getProfile(); } } diff --git a/version.txt b/version.txt index 0fa4ae48..712bd5a6 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.3.0 \ No newline at end of file +3.3.1 \ No newline at end of file