Skip to content

Commit 26802cb

Browse files
Offline player fetching improvements (#117)
1 parent 1cb35ab commit 26802cb

13 files changed

Lines changed: 148 additions & 62 deletions

File tree

‎bungee/src/main/java/eu/kennytv/maintenance/bungee/MaintenanceBungeePlugin.java‎

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@
6161
import java.util.List;
6262
import java.util.Set;
6363
import java.util.UUID;
64+
import java.util.concurrent.CompletableFuture;
6465
import java.util.concurrent.TimeUnit;
65-
import java.util.function.Consumer;
6666
import java.util.logging.Logger;
6767
import java.util.stream.Collectors;
6868

@@ -176,28 +176,26 @@ public Task startMaintenanceRunnable(final Runnable runnable) {
176176
}
177177

178178
@Override
179-
public void getOfflinePlayer(final String name, final Consumer<@Nullable SenderInfo> consumer) {
179+
public CompletableFuture<@Nullable SenderInfo> getOfflinePlayer(final String name) {
180180
final ProxiedPlayer player = getProxy().getPlayer(name);
181181
if (player != null) {
182-
consumer.accept(new BungeeSenderInfo(player));
183-
return;
182+
return CompletableFuture.completedFuture(new BungeeSenderInfo(player));
184183
}
185184

186-
getProxy().getScheduler().runAsync(plugin, () -> {
185+
return CompletableFuture.supplyAsync(() -> {
187186
try {
188187
final ProfileLookup profile = doUUIDLookup(name);
189-
consumer.accept(new ProxyOfflineSenderInfo(profile.getUuid(), profile.getName()));
190-
} catch (final IOException e) {
191-
e.printStackTrace();
192-
consumer.accept(null);
188+
return new ProxyOfflineSenderInfo(profile.getUuid(), profile.getName());
189+
} catch (IOException e) {
190+
throw new RuntimeException(e);
193191
}
194192
});
195193
}
196194

197195
@Override
198-
public void getOfflinePlayer(final UUID uuid, final Consumer<@Nullable SenderInfo> consumer) {
196+
public CompletableFuture<@Nullable SenderInfo> getOfflinePlayer(final UUID uuid) {
199197
final ProxiedPlayer player = getProxy().getPlayer(uuid);
200-
consumer.accept(player != null ? new BungeeSenderInfo(player) : null);
198+
return CompletableFuture.completedFuture(player != null ? new BungeeSenderInfo(player) : null);
201199
}
202200

203201
@Override

‎core-proxy/src/main/java/eu/kennytv/maintenance/core/proxy/MaintenanceProxyPlugin.java‎

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,19 @@
2828
import eu.kennytv.maintenance.core.proxy.runnable.SingleMaintenanceScheduleRunnable;
2929
import eu.kennytv.maintenance.core.proxy.util.ProfileLookup;
3030
import eu.kennytv.maintenance.core.runnable.MaintenanceRunnableBase;
31+
import eu.kennytv.maintenance.core.util.RateLimitedException;
3132
import eu.kennytv.maintenance.core.util.SenderInfo;
3233
import eu.kennytv.maintenance.core.util.ServerType;
3334
import eu.kennytv.maintenance.core.util.Task;
35+
import org.jetbrains.annotations.Blocking;
3436
import org.jetbrains.annotations.Nullable;
3537

3638
import java.io.IOException;
3739
import java.io.InputStream;
3840
import java.io.InputStreamReader;
41+
import java.net.HttpURLConnection;
3942
import java.net.URL;
40-
import java.net.URLConnection;
43+
import java.nio.charset.StandardCharsets;
4144
import java.util.ArrayList;
4245
import java.util.Collections;
4346
import java.util.HashMap;
@@ -187,9 +190,66 @@ protected void kickPlayers() {
187190
kickPlayersFromProxy();
188191
}
189192

193+
@Blocking
194+
@Nullable
190195
protected ProfileLookup doUUIDLookup(final String name) throws IOException {
196+
ProfileLookup profileLookup;
197+
try {
198+
profileLookup = doUUIDLookupMojangAPI(name);
199+
} catch (RateLimitedException e) {
200+
// Use fallback API if rate limit is reached
201+
profileLookup = doUUIDLookupAshconAPI(name);
202+
}
203+
204+
if (settingsProxy.isFallbackToOfflineUUID() && profileLookup == null) {
205+
// Use offline uuid
206+
return new ProfileLookup(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8)), name);
207+
}
208+
209+
return profileLookup;
210+
}
211+
212+
/**
213+
* Official Mojang API
214+
*/
215+
@Nullable
216+
private ProfileLookup doUUIDLookupMojangAPI(final String name) throws IOException {
217+
final URL url = new URL("https://api.mojang.com/users/profiles/minecraft/" + name);
218+
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
219+
connection.setRequestMethod("GET");
220+
221+
int status = connection.getResponseCode();
222+
if (status == 429) {
223+
throw new RateLimitedException();
224+
}
225+
if (status == 404) {
226+
// Return null if profile not found
227+
return null;
228+
}
229+
230+
try (final InputStream in = connection.getInputStream()) {
231+
final String output = CharStreams.toString(new InputStreamReader(in));
232+
final JsonObject json = GSON.fromJson(output, JsonObject.class);
233+
234+
final UUID uuid = fromStringUUIDWithoutDashes(json.getAsJsonPrimitive("id").getAsString());
235+
final String username = json.getAsJsonPrimitive("name").getAsString();
236+
return new ProfileLookup(uuid, username);
237+
}
238+
}
239+
240+
/**
241+
* Fallback API (Ashcon API)
242+
*/
243+
@Nullable
244+
private ProfileLookup doUUIDLookupAshconAPI(final String name) throws IOException {
191245
final URL url = new URL("https://api.ashcon.app/mojang/v2/user/" + name);
192-
final URLConnection connection = url.openConnection();
246+
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
247+
248+
if (connection.getResponseCode() == 403) {
249+
// Return null if profile not found
250+
return null;
251+
}
252+
193253
try (final InputStream in = connection.getInputStream()) {
194254
final String output = CharStreams.toString(new InputStreamReader(in));
195255
final JsonObject json = GSON.fromJson(output, JsonObject.class);
@@ -200,6 +260,14 @@ protected ProfileLookup doUUIDLookup(final String name) throws IOException {
200260
}
201261
}
202262

263+
private UUID fromStringUUIDWithoutDashes(String undashedUUID) {
264+
return UUID.fromString(
265+
undashedUUID.substring(0, 8) + "-" + undashedUUID.substring(8, 12) + "-" +
266+
undashedUUID.substring(12, 16) + "-" + undashedUUID.substring(16, 20) + "-" +
267+
undashedUUID.substring(20, 32)
268+
);
269+
}
270+
203271
public SettingsProxy getSettingsProxy() {
204272
return settingsProxy;
205273
}

‎core-proxy/src/main/java/eu/kennytv/maintenance/core/proxy/SettingsProxy.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public final class SettingsProxy extends Settings {
3939
private Set<String> maintenanceServers;
4040
private List<String> fallbackServers;
4141
private String waitingServer;
42+
private boolean fallbackToOfflineUUID;
4243

4344
private Map<String, List<String>> commandsOnMaintenanceEnable;
4445
private Map<String, List<String>> commandsOnMaintenanceDisable;
@@ -103,6 +104,7 @@ protected void loadExtraSettings() {
103104
if (waitingServer.isEmpty() || waitingServer.equalsIgnoreCase("none")) {
104105
waitingServer = null;
105106
}
107+
fallbackToOfflineUUID = config.getBoolean("fallback-to-offline-uuid", false);
106108

107109
commandsOnMaintenanceEnable = new HashMap<>();
108110
final ConfigSection enableCommandsSection = config.getSection("commands-on-single-maintenance-enable");
@@ -279,6 +281,10 @@ public String getWaitingServer() {
279281
return waitingServer;
280282
}
281283

284+
public boolean isFallbackToOfflineUUID() {
285+
return fallbackToOfflineUUID;
286+
}
287+
282288
public List<String> getCommandsOnMaintenanceEnable(final Server server) {
283289
final List<String> enableCommands = commandsOnMaintenanceEnable.getOrDefault("all", new ArrayList<>());
284290
final List<String> serverEnableCommands = commandsOnMaintenanceEnable.get(server.getName().toLowerCase(Locale.ROOT));

‎core/src/main/java/eu/kennytv/maintenance/core/MaintenancePlugin.java‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@
5757
import java.util.Arrays;
5858
import java.util.List;
5959
import java.util.UUID;
60+
import java.util.concurrent.CompletableFuture;
6061
import java.util.concurrent.TimeUnit;
61-
import java.util.function.Consumer;
6262
import java.util.logging.Level;
6363
import java.util.logging.Logger;
6464

@@ -447,9 +447,9 @@ protected String getPluginFolder() {
447447
*
448448
* @param name name of the player
449449
*/
450-
public abstract void getOfflinePlayer(String name, Consumer<@Nullable SenderInfo> consumer);
450+
public abstract CompletableFuture<@Nullable SenderInfo> getOfflinePlayer(String name);
451451

452-
public abstract void getOfflinePlayer(UUID uuid, Consumer<@Nullable SenderInfo> consumer);
452+
public abstract CompletableFuture<@Nullable SenderInfo> getOfflinePlayer(UUID uuid);
453453

454454
public abstract File getDataFolder();
455455

‎core/src/main/java/eu/kennytv/maintenance/core/Settings.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@
4141

4242
public class Settings implements eu.kennytv.maintenance.api.Settings {
4343
public static final String NEW_LINE_REPLACEMENT = "<br>";
44-
private static final int CONFIG_VERSION = 7;
45-
private static final int LANGUAGE_VERSION = 1;
44+
private static final int CONFIG_VERSION = 8;
45+
private static final int LANGUAGE_VERSION = 2;
4646
private static final Random RANDOM = new Random();
4747
protected final MaintenancePlugin plugin;
4848
private final Map<UUID, String> whitelistedPlayers = new HashMap<>();

‎core/src/main/java/eu/kennytv/maintenance/core/command/subcommand/WhitelistAddCommand.java‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.Collections;
2626
import java.util.List;
2727
import java.util.UUID;
28+
import java.util.logging.Level;
2829

2930
public final class WhitelistAddCommand extends CommandInfo {
3031

@@ -64,7 +65,13 @@ public List<String> getTabCompletion(final SenderInfo sender, final String[] arg
6465
}
6566

6667
private void addPlayerToWhitelist(final SenderInfo sender, final String name) {
67-
plugin.getOfflinePlayer(name, selected -> {
68+
plugin.getOfflinePlayer(name).whenComplete((selected, ex) -> {
69+
if (ex != null) {
70+
plugin.getLogger().log(Level.SEVERE, "Error while fetching offline player", ex);
71+
sender.send(getMessage("offlinePlayerFetchError"));
72+
return;
73+
}
74+
6875
if (selected == null) {
6976
sender.send(getMessage("playerNotOnline"));
7077
return;
@@ -75,7 +82,13 @@ private void addPlayerToWhitelist(final SenderInfo sender, final String name) {
7582
}
7683

7784
private void addPlayerToWhitelist(final SenderInfo sender, final UUID uuid) {
78-
plugin.getOfflinePlayer(uuid, selected -> {
85+
plugin.getOfflinePlayer(uuid).whenComplete((selected, ex) -> {
86+
if (ex != null) {
87+
plugin.getLogger().log(Level.SEVERE, "Error while fetching offline player", ex);
88+
sender.send(getMessage("offlinePlayerFetchError"));
89+
return;
90+
}
91+
7992
if (selected == null) {
8093
sender.send(getMessage("playerNotFoundUuid"));
8194
return;

‎core/src/main/java/eu/kennytv/maintenance/core/command/subcommand/WhitelistRemoveCommand.java‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,17 @@ public List<String> getTabCompletion(final SenderInfo sender, final String[] arg
5050
if (args.length != 2) return Collections.emptyList();
5151

5252
final List<String> list = new ArrayList<>();
53+
String matchNameLowerCase = args[1].toLowerCase();
5354
for (final String name : getSettings().getWhitelistedPlayers().values()) {
54-
if (name.toLowerCase().startsWith(args[1])) {
55+
if (name.toLowerCase().startsWith(matchNameLowerCase)) {
5556
list.add(name);
5657
}
5758
}
5859
return list;
5960
}
6061

6162
private void removePlayerFromWhitelist(final SenderInfo sender, final String name) {
62-
plugin.getOfflinePlayer(name, selected -> {
63+
plugin.getOfflinePlayer(name).whenComplete((selected, ex) -> {
6364
if (selected == null) {
6465
if (getSettings().removeWhitelistedPlayer(name)) {
6566
sender.send(getMessage("whitelistRemoved", "%PLAYER%", name));
@@ -74,7 +75,7 @@ private void removePlayerFromWhitelist(final SenderInfo sender, final String nam
7475
}
7576

7677
private void removePlayerFromWhitelist(final SenderInfo sender, final UUID uuid) {
77-
plugin.getOfflinePlayer(uuid, selected -> {
78+
plugin.getOfflinePlayer(uuid).whenComplete((selected, ex) -> {
7879
if (selected == null) {
7980
removePlayerFromWhitelist(sender, uuid, uuid.toString());
8081
} else {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package eu.kennytv.maintenance.core.util;
2+
3+
import java.io.IOException;
4+
5+
/**
6+
* Exception thrown when an API rate limit is reached.
7+
*/
8+
public class RateLimitedException extends IOException {
9+
}

‎core/src/main/resources/config.yml‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ send-join-notification: false
8282
# ... I don't know why you would want that, but you can disable it. :p
8383
kick-online-players: true
8484

85+
# When fetched player does not exist then fallback to offline uuid. Only works on proxies like Velocity or BungeeCord
86+
fallback-to-offline-uuid: false
87+
8588
# Changes the language of command feedback/messages.
8689
# If you find missing translations or want to contribute a new language file, you are very welcome to message me on Spigot or my Discord server! :)
8790
# Currently available are: en (English), de (German), fr (French), pt (Portuguese), es (Spanish), ru (Russian), zh (Chinese), it (Italian),
@@ -123,4 +126,4 @@ timer-broadcast-for-seconds: [1200, 900, 600, 300, 120, 60, 30, 20, 10, 5, 4, 3,
123126
update-checks: true
124127

125128
# Used for autoupdating the config, do not change this value.
126-
config-version: 7
129+
config-version: 8

‎core/src/main/resources/language-en.yml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ whitelistEmpty: "<prefix><red>The maintenance whitelist is empty! Use <yellow>/m
4444
playerNotFound: "<prefix><red>No player with this name has played on this server before."
4545
playerNotFoundUuid: "<prefix><red>No player with that uuid could be found."
4646
playerNotOnline: "<prefix><red>There is no player online with that name."
47+
offlinePlayerFetchError: "<prefix><red>There was an error while fetching offline player. Please try again later."
4748
invalidUuid: "<prefix><red>Invalid uuid format!"
4849
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Spigot/Sponge
4950
sentToWaitingServer: "<prefix><red>You have been sent to a waiting server!"
@@ -100,4 +101,4 @@ helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server] <timer minut
100101
helpSingleToggle: "<gold>/maintenance <on/off> [server] <gray>(Enables/disables maintenance mode)"
101102
helpStatus: "<gold>/maintenance status <gray>(Lists all proxied servers, that are currently under maintenance)"
102103
#Used for autoupdating the language file, do not change this value.
103-
language-version: 1
104+
language-version: 2

0 commit comments

Comments
 (0)