diff --git a/README.md b/README.md
index 26740d0..b2a0df3 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# An API to expose player information for Janet (or other discord bots).
@@ -131,3 +132,6 @@ Note, the plugin will not run unless you change the secret. This is explained ab
# Legal Mumbo Jumbo
The idea and base code for this project came from [TristanSMPAPI](https://github.com/twisttaan/TristanSMPAPI).
It has been extensively added to by myself with much technical help from the developers of [DiscordSRV](https://github.com/DiscordSRV/DiscordSRV/)
+=======
+A remake of BoredManCodes' SMP-API to fit my needs.
+>>>>>>> 5b19083 (file add)
diff --git a/pom.xml b/pom.xml
index d5d5f4a..8a699cc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,9 +4,15 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
+<<<<<<< HEAD
net.boredman
SMP-API
2.0.3
+=======
+ quest.safecloud
+ SMP-API
+ 1.0
+>>>>>>> 5b19083 (file add)
jar
SMP-Api
@@ -16,7 +22,11 @@
17
UTF-8
+<<<<<<< HEAD
https://boredman.net
+=======
+ https://safecloud.quest
+>>>>>>> 5b19083 (file add)
@@ -89,7 +99,7 @@
com.discordsrv
discordsrv
- 1.21.1
+ 1.27.0
provided
diff --git a/src/main/java/net/boredman/api.java b/src/main/java/net/boredman/api.java
index f4d53ea..cfb4414 100644
--- a/src/main/java/net/boredman/api.java
+++ b/src/main/java/net/boredman/api.java
@@ -44,6 +44,4 @@ public void start() {
}
public static Express express = new Express();
-
-
}
diff --git a/src/main/java/net/boredman/routes/DiscordRoute.java b/src/main/java/net/boredman/routes/DiscordRoute.java
index 951d5ff..44c3877 100644
--- a/src/main/java/net/boredman/routes/DiscordRoute.java
+++ b/src/main/java/net/boredman/routes/DiscordRoute.java
@@ -30,26 +30,23 @@ public DiscordRoute(Express app) {
API.getPlugin(API.class).getLogger().info("A request was made to access " +
req.getParams().get("username") + "'s Discord data");
}
- if (!secret.equals(req.getHeader("secret").get(0))) {
+ if (secret!= null &&!secret.equals(req.getHeader("secret").get(0))) {
obj.put("error", true);
obj.put("message", "You are not authorised to access this resource");
res.send(obj.toJSONString());
API.getPlugin(API.class).getLogger().warning("A request to access Discord info from " + req.getIp() +
" was rejected as they did not pass the correct secret in the header");
- return;
} else {
if (discordId == null) {
obj.put("error", true);
obj.put("message", "Player not linked to discord");
res.send(obj.toJSONString());
- return;
} else {
User user = DiscordUtil.getJda().getUserById(discordId);
if (user == null) {
obj.put("error", true);
obj.put("message", "Couldn't find Discord User by ID. Maybe they left the server?");
res.send(obj.toJSONString());
- return;
} else {
obj.put("error", false);
obj.put("username", username);
@@ -58,7 +55,6 @@ public DiscordRoute(Express app) {
obj.put("discordTag", user.getAsTag());
obj.put("discordName", user.getName());
res.send(obj.toJSONString());
- return;
}
}
}
@@ -74,7 +70,7 @@ public DiscordRoute(Express app) {
API.getPlugin(API.class).getLogger().info("A request was made to access " +
req.getParams().get("id") + "'s Discord data");
}
- if (!secret.equals(req.getHeader("secret").get(0))) {
+ if (secret!= null &&!secret.equals(req.getHeader("secret").get(0))) {
obj.put("error", true);
obj.put("message", "You are not authorised to access this resource");
res.send(obj.toJSONString());
@@ -85,14 +81,12 @@ public DiscordRoute(Express app) {
obj.put("error", true);
obj.put("message", "Player not linked to discord");
res.send(obj.toJSONString());
- return;
} else {
User user = DiscordUtil.getJda().getUserById(discordId);
if (user == null) {
obj.put("error", true);
obj.put("message", "Couldn't find Discord User by ID. Maybe they left the server?");
res.send(obj.toJSONString());
- return;
} else {
obj.put("error", false);
obj.put("username", username);
@@ -101,7 +95,6 @@ public DiscordRoute(Express app) {
obj.put("discordTag", user.getAsTag());
obj.put("discordName", user.getName());
res.send(obj.toJSONString());
- return;
}
}
}
diff --git a/src/main/java/quest/safecloud/API.java b/src/main/java/quest/safecloud/API.java
new file mode 100644
index 0000000..57d2018
--- /dev/null
+++ b/src/main/java/quest/safecloud/API.java
@@ -0,0 +1,49 @@
+package quest.safecloud;
+
+import express.Express;
+import github.scarsz.discordsrv.objects.managers.AccountLinkManager;
+import quest.safecloud.events.QuitEvent;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.event.Listener;
+import org.bukkit.plugin.java.JavaPlugin;
+
+
+public class API extends JavaPlugin implements Listener {
+ private static API plugin;
+ final FileConfiguration config = getConfig();
+
+ public static Express getApp() {
+ return express;
+ }
+
+ @Override
+ public void onEnable() {
+ config.addDefault("port", 25567);
+ config.addDefault("secret", "CHANGE THIS!");
+ config.addDefault("debug", false);
+ config.options().copyDefaults(true);
+ saveConfig();
+ start();
+ plugin = this;
+ if (config.getString("secret").equals("CHANGE THIS!")) {
+ getLogger().warning("--------------------------------------------");
+ getLogger().severe("You MUST change the secret in the config.yml for this plugin to work. " +
+ "This prevents exposing player IP addresses to the world");
+ getLogger().warning("--------------------------------------------");
+ this.getPluginLoader().disablePlugin(this);
+ }
+ this.getServer().getPluginManager().registerEvents(new QuitEvent(), this);
+ }
+
+ @Override
+ public void onDisable() {
+ }
+
+ public void start() {
+ getLogger().info("Starting API on port " + config.getInt("port"));
+ new ReqHandler(express);
+ getLogger().info("API Started");
+ }
+
+ public static Express express = new Express();
+}
diff --git a/src/main/java/quest/safecloud/ReqHandler.java b/src/main/java/quest/safecloud/ReqHandler.java
new file mode 100644
index 0000000..5b875b3
--- /dev/null
+++ b/src/main/java/quest/safecloud/ReqHandler.java
@@ -0,0 +1,16 @@
+package quest.safecloud;
+
+import express.Express;
+import quest.safecloud.routes.DiscordRoute;
+import quest.safecloud.routes.PlayersRoute;
+
+public class ReqHandler {
+
+ public ReqHandler(Express app) {
+ int port = API.getPlugin(API.class).getConfig().getInt("port");
+ new PlayersRoute(app);
+ new DiscordRoute(app);
+ app.get("/", (req, res) -> res.send("Error 418: The server refuses to brew coffee because it is, permanently, a teapot.\n" +
+ "For more information: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418")).listen(port);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/quest/safecloud/events/QuitEvent.java b/src/main/java/quest/safecloud/events/QuitEvent.java
new file mode 100644
index 0000000..16b04b3
--- /dev/null
+++ b/src/main/java/quest/safecloud/events/QuitEvent.java
@@ -0,0 +1,78 @@
+package quest.safecloud.events;
+
+import quest.safecloud.API;
+import org.bukkit.Statistic;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.json.simple.JSONObject;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import static org.bukkit.Bukkit.getPlayer;
+
+public class QuitEvent implements Listener {
+ @EventHandler
+ public void playerQuitEvent(PlayerQuitEvent event) throws IOException {
+ boolean debug = API.getPlugin(API.class).getConfig().getBoolean("debug");
+ if (debug) {
+ System.out.println(event.getPlayer().getName().toString() + " left the server, saving their player data.");
+ }
+ File dir = new File(API.class.getProtectionDomain().getCodeSource().getLocation().getPath().replaceAll("%20", " "));
+ File plugins = new File(dir.getParentFile().getPath());
+ String playerDataFolder = plugins + "\\SMP-API\\playerdata\\";
+ if (!Files.exists(Path.of(playerDataFolder))) {
+ Files.createDirectory(Path.of(playerDataFolder));
+ }
+ String filename = playerDataFolder + event.getPlayer().getName() + ".json";
+ String username = event.getPlayer().getName();
+ JSONObject obj = new JSONObject();
+ Player player = getPlayer(username);
+ String bed;
+ String[] arrOfBed;
+ String location;
+ String[] arrOfLocation;
+ String address;
+ String[] arrOfAddress;
+ try {
+ obj.put((Object) "username", (Object) player.getName());
+ obj.put((Object) "uuid", (Object) player.getUniqueId().toString());
+ obj.put((Object) "health", (Object) String.valueOf(player.getHealth()));
+ obj.put((Object) "food", (Object) String.valueOf(player.getFoodLevel()));
+ obj.put((Object) "world", (Object) player.getWorld().getName());
+ obj.put((Object) "experience", (Object) String.valueOf(player.getExp()));
+ obj.put((Object) "level", (Object) String.valueOf(player.getLevel()));
+ obj.put((Object) "deaths", (Object) String.valueOf(player.getStatistic(Statistic.DEATHS)));
+ obj.put((Object) "kills", (Object) String.valueOf(player.getStatistic(Statistic.MOB_KILLS)));
+ obj.put((Object) "jumps", (Object) String.valueOf(player.getStatistic(Statistic.JUMP)));
+ obj.put((Object) "gamemode", (Object) player.getGameMode().toString());
+ if (player.getBedSpawnLocation() != null) {
+ bed = player.getBedSpawnLocation().toString();
+ arrOfBed = bed.split(",");
+ obj.put((Object) "bed", (Object) (arrOfBed[1] + "," + arrOfBed[2] + "," + arrOfBed[3]));
+ }
+ obj.put((Object) "time", (Object) String.valueOf(player.getStatistic(Statistic.PLAY_ONE_MINUTE) / 20));
+ obj.put((Object) "death", (Object) String.valueOf(player.getStatistic(Statistic.TIME_SINCE_DEATH) / 20));
+ address = String.valueOf(player.getAddress()).replace("/", "");
+ arrOfAddress = address.split(":");
+ obj.put((Object) "address", (Object) arrOfAddress[0]);
+ obj.put((Object) "lastJoined", (Object) System.currentTimeMillis());
+ obj.put((Object) "online", (Object) false);
+ location = player.getLocation().toString();
+ arrOfLocation = location.split(",");
+ obj.put((Object) "location", (Object) (arrOfLocation[1] + "," + arrOfLocation[2] + "," + arrOfLocation[3]));
+ Files.write(Paths.get(filename), obj.toJSONString().getBytes());
+ if (debug) {
+ API.getPlugin(API.class).getLogger().info("Saved " + event.getPlayer().getName() + "'s player data");
+ }
+ } catch (Exception e) {
+ API.getPlugin(API.class).getLogger().severe("Ran into an error trying to save player data");
+ API.getPlugin(API.class).getLogger().severe(String.valueOf(e));
+ }
+ }
+}
diff --git a/src/main/java/quest/safecloud/routes/DiscordRoute.java b/src/main/java/quest/safecloud/routes/DiscordRoute.java
new file mode 100644
index 0000000..2c30e8c
--- /dev/null
+++ b/src/main/java/quest/safecloud/routes/DiscordRoute.java
@@ -0,0 +1,108 @@
+package quest.safecloud.routes;
+
+import express.Express;
+import github.scarsz.discordsrv.dependencies.jda.api.entities.User;
+import github.scarsz.discordsrv.util.DiscordUtil;
+import quest.safecloud.API;
+import org.bukkit.OfflinePlayer;
+import org.json.simple.JSONObject;
+
+import java.util.UUID;
+
+import static github.scarsz.discordsrv.DiscordSRV.getPlugin;
+import static org.bukkit.Bukkit.getOfflinePlayer;
+
+
+@SuppressWarnings("unchecked")
+public class DiscordRoute {
+ public DiscordRoute(Express app) {
+
+ // Read config
+ boolean debug = API.getPlugin(API.class).getConfig().getBoolean("debug");
+ String secret = API.getPlugin(API.class).getConfig().getString("secret");
+
+ if (secret == null) {
+ API.getPlugin(API.class).getLogger().warning("Secret not set in config.yml. This is a security risk.");
+ return;
+ }
+
+ // Lookup Discord via username
+ app.get("/minecraft/name/:username", (req, res) -> {
+ final String username = req.getParams().get("username");
+ final OfflinePlayer player = getOfflinePlayer(username);
+ final String discordId = getPlugin().getAccountLinkManager().getDiscordId(player.getUniqueId());
+ final JSONObject obj = new JSONObject();
+ if (debug) {
+ API.getPlugin(API.class).getLogger().info("A request was made to access " +
+ req.getParams().get("username") + "'s Discord data");
+ }
+ if (!secret.equals(req.getHeader("secret").get(0))) {
+ obj.put("error", true);
+ obj.put("message", "You are not authorised to access this resource");
+ res.send(obj.toJSONString());
+ API.getPlugin(API.class).getLogger().warning("A request to access Discord info from " + req.getIp() +
+ " was rejected as they did not pass the correct secret in the header");
+ } else {
+ if (discordId == null) {
+ obj.put("error", true);
+ obj.put("message", "Player not linked to discord");
+ } else {
+ User user = DiscordUtil.getJda().getUserById(discordId);
+ if (user == null) {
+ obj.put("error", true);
+ obj.put("message", "Couldn't find Discord User by ID. Maybe they left the server?");
+ } else {
+ obj.put("error", false);
+ obj.put("username", username);
+ obj.put("uuid", player.getUniqueId().toString());
+ obj.put("discordId", discordId);
+ obj.put("discordTag", user.getAsTag());
+ obj.put("discordName", user.getName());
+ }
+ }
+ res.send(obj.toJSONString());
+ }
+ });
+
+ // Lookup Discord via ID
+ app.get("/discord/id/:id", (req, res) -> {
+ final String discordId = req.getParams().get("id");
+ final UUID playerUUID = getPlugin().getAccountLinkManager().getUuid(discordId);
+ final JSONObject obj = new JSONObject();
+ if (debug) {
+ API.getPlugin(API.class).getLogger().info("A request was made to access " +
+ req.getParams().get("id") + "'s Discord data");
+ }
+ if (!secret.equals(req.getHeader("secret").get(0))) {
+ obj.put("error", true);
+ obj.put("message", "You are not authorised to access this resource");
+ res.send(obj.toJSONString());
+ API.getPlugin(API.class).getLogger().warning("A request to access Discord info from " + req.getIp() +
+ " was rejected as they did not pass the correct secret in the header");
+ } else {
+ if (playerUUID == null) {
+ obj.put("error", true);
+ obj.put("message", "Player not linked to discord");
+ res.send(obj.toJSONString());
+ } else {
+ User user = DiscordUtil.getJda().getUserById(discordId);
+ if (user == null) {
+ obj.put("error", true);
+ obj.put("message", "Couldn't find Discord User by ID. Maybe they left the server?");
+ res.send(obj.toJSONString());
+ } else {
+ OfflinePlayer player = getOfflinePlayer(playerUUID);
+ obj.put("error", false);
+ obj.put("username", player.getName());
+ obj.put("uuid", player.getUniqueId().toString());
+ obj.put("discordId", discordId);
+ obj.put("discordTag", user.getAsTag());
+ obj.put("discordName", user.getName());
+ res.send(obj.toJSONString());
+ }
+ }
+ }
+ });
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/quest/safecloud/routes/PlayersRoute.java b/src/main/java/quest/safecloud/routes/PlayersRoute.java
new file mode 100644
index 0000000..1b5910f
--- /dev/null
+++ b/src/main/java/quest/safecloud/routes/PlayersRoute.java
@@ -0,0 +1,130 @@
+package net.boredman.routes;
+
+import express.Express;
+import github.scarsz.discordsrv.dependencies.jda.api.entities.User;
+import github.scarsz.discordsrv.util.DiscordUtil;
+import net.boredman.API;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.Statistic;
+import org.bukkit.entity.Player;
+import org.json.simple.JSONObject;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.UUID;
+
+import static github.scarsz.discordsrv.DiscordSRV.getPlugin;
+import static org.bukkit.Bukkit.*;
+
+public class PlayersRoute {
+ public PlayersRoute(Express app) {
+
+ // Online Player Stats //
+ boolean debug = API.getPlugin(API.class).getConfig().getBoolean("debug");
+ String secret = API.getPlugin(API.class).getConfig().getString("secret");
+ app.get("/players/:username", (req, res) -> {
+ if (debug) {
+ API.getPlugin(API.class).getLogger().info("A request was made to access " +
+ req.getParams().get("username") + "'s player data");
+ }
+ if (secret.equals(req.getHeader("secret").get(0))) {
+ File dir = new File(API.class.getProtectionDomain().getCodeSource().getLocation().getPath().replaceAll("%20", " "));
+ File plugins = new File(dir.getParentFile().getPath());
+ String playerDataFolder = plugins + "\\SMP-API\\playerdata\\";
+ if (!Files.exists(Path.of(playerDataFolder))) {
+ try {
+ Files.createDirectory(Path.of(playerDataFolder));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ String username = req.getParams().get("username");
+ JSONObject obj = new JSONObject();
+ Player player = getPlayer(username);
+ String bed;
+ String[] arrOfBed;
+ String location;
+ String[] arrOfLocation;
+ String address;
+ String[] arrOfAddress;
+ try {
+ obj.put((Object)"username", (Object)player.getName());
+ obj.put((Object)"uuid", (Object)player.getUniqueId().toString());
+ obj.put((Object)"health", (Object)String.valueOf(player.getHealth()));
+ obj.put((Object)"food", (Object)String.valueOf(player.getFoodLevel()));
+ obj.put((Object)"world", (Object)player.getWorld().getName());
+ obj.put((Object)"experience", (Object)String.valueOf(player.getExp()));
+ obj.put((Object)"level", (Object)String.valueOf(player.getLevel()));
+ obj.put((Object)"deaths", (Object)String.valueOf(player.getStatistic(Statistic.DEATHS)));
+ obj.put((Object)"kills", (Object)String.valueOf(player.getStatistic(Statistic.MOB_KILLS)));
+ obj.put((Object)"jumps", (Object)String.valueOf(player.getStatistic(Statistic.JUMP)));
+ obj.put((Object)"gamemode", (Object)player.getGameMode().toString());
+ if (player.getBedSpawnLocation() != null) {
+ bed = player.getBedSpawnLocation().toString();
+ arrOfBed = bed.split(",");
+ obj.put((Object)"bed", (Object)(arrOfBed[1] + "," + arrOfBed[2] + "," + arrOfBed[3]));
+ }
+ obj.put((Object)"time", (Object)String.valueOf(player.getStatistic(Statistic.PLAY_ONE_MINUTE) / 20));
+ obj.put((Object)"death", (Object)String.valueOf(player.getStatistic(Statistic.TIME_SINCE_DEATH) / 20));
+ address = String.valueOf(player.getAddress()).replace("/", "");
+ arrOfAddress = address.split(":");
+ obj.put((Object)"address", (Object)arrOfAddress[0]);
+ obj.put((Object)"lastJoined", (Object)player.getLastPlayed());
+ obj.put((Object) "online", (Object) true);
+ location = player.getLocation().toString();
+ arrOfLocation = location.split(",");
+ obj.put((Object) "location", (Object) (arrOfLocation[1] + "," + arrOfLocation[2] + "," + arrOfLocation[3]));
+ res.send(obj.toJSONString());
+ } catch (Exception e) {
+ if (e.getMessage().contains("Cannot invoke \"org.bukkit.entity.Player.getName()\" because \"player\" is null")) {
+ if (debug) {
+ API.getPlugin(API.class).getLogger().info("Player offline, attempting to serve cached player data");
+ }
+ String filename = playerDataFolder + username + ".json";
+ if (Files.exists(Path.of(filename))) {
+ String content = "blank";
+ try {
+ content = Files.readString(Path.of(filename));
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ res.send(content);
+ BasicFileAttributes attr = null;
+ try {
+ attr = Files.readAttributes(Path.of(filename), BasicFileAttributes.class);
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+
+ if (debug) {
+ if (attr != null) {
+ API.getPlugin(API.class).getLogger().info("Served cached data from " + attr.lastModifiedTime());
+ } else {
+ API.getPlugin(API.class).getLogger().info("Served cached data");
+ }
+ }
+ return;
+ }
+ obj.put("error", true);
+ obj.put("message", "Player not online, or not found");
+ res.send(obj.toJSONString());
+ } else {
+ getLogger().info("Error: " + e.getMessage());
+ res.send("Error: " + e.getMessage());
+ }
+ }
+ } else {
+ final JSONObject obj = new JSONObject();
+ obj.put("error", true);
+ obj.put("message", "You are not authorised to access this resource");
+ res.send(obj.toJSONString());
+ API.getPlugin(API.class).getLogger().warning("A request to access Minecraft info from " + req.getIp() +
+ " was rejected as they did not pass the correct secret in the header");
+ }
+ });
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 1340749..1b70789 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -1,10 +1,10 @@
name: SMP-API
version: '${project.version}'
-main: net.boredman.API
-api-version: 1.18
+main: quest.safecloud.API
+api-version: 1.20
prefix: SMP-API
-authors: [ BoredManCodes, twisttaan ]
+authors: [ Wingdingderp, BoredManCodes, twisttaan ]
softdepend:
- 'DiscordSRV'
description: A SMP API for exposing player information for Discord bots to use
-website: https://boredman.net
+website: https://safecloud.quest