mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-25 09:39:18 +00:00
Add /userdata dump command, for file/web dumping of user data json
This commit is contained in:
@@ -15,6 +15,7 @@ import net.william278.husksync.util.ResourceReader;
|
||||
import net.william278.desertwell.Version;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@@ -134,6 +135,14 @@ public interface HuskSync {
|
||||
@NotNull
|
||||
Version getPluginVersion();
|
||||
|
||||
/**
|
||||
* Returns the plugin data folder
|
||||
*
|
||||
* @return the plugin data folder as a {@link File}
|
||||
*/
|
||||
@NotNull
|
||||
File getDataFolder();
|
||||
|
||||
/**
|
||||
* Returns a future returning the latest plugin {@link Version} if the plugin is out-of-date
|
||||
*
|
||||
|
||||
@@ -154,7 +154,7 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
||||
public List<String> onTabComplete(@NotNull String[] args) {
|
||||
if (args.length <= 1) {
|
||||
return Arrays.stream(SUB_COMMANDS)
|
||||
.filter(argument -> argument.startsWith(args.length >= 1 ? args[0] : ""))
|
||||
.filter(argument -> argument.startsWith(args.length == 1 ? args[0] : ""))
|
||||
.sorted().collect(Collectors.toList());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
|
||||
@@ -41,6 +41,11 @@ public enum Permission {
|
||||
*/
|
||||
COMMAND_USER_DATA_MANAGE("husksync.command.userdata.manage", DefaultAccess.OPERATORS),
|
||||
|
||||
/**
|
||||
* Lets the user dump user data to a file or the web {@code /userdata dump (player) (version_uuid)}
|
||||
*/
|
||||
COMMAND_USER_DATA_DUMP("husksync.command.userdata.dump", DefaultAccess.OPERATORS),
|
||||
|
||||
/*
|
||||
* /inventory command permissions
|
||||
*/
|
||||
|
||||
@@ -3,18 +3,21 @@ package net.william278.husksync.command;
|
||||
import net.william278.husksync.HuskSync;
|
||||
import net.william278.husksync.data.DataSaveCause;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import net.william278.husksync.util.DataDumper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class UserDataCommand extends CommandBase implements TabCompletable {
|
||||
|
||||
private final String[] COMMAND_ARGUMENTS = {"view", "list", "delete", "restore", "pin"};
|
||||
private final String[] COMMAND_ARGUMENTS = {"view", "list", "delete", "restore", "pin", "dump"};
|
||||
|
||||
public UserDataCommand(@NotNull HuskSync implementor) {
|
||||
super("userdata", Permission.COMMAND_USER_DATA, implementor, "playerdata");
|
||||
@@ -24,7 +27,7 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
||||
public void onExecute(@NotNull OnlineUser player, @NotNull String[] args) {
|
||||
if (args.length < 1) {
|
||||
plugin.getLocales().getLocale("error_invalid_syntax",
|
||||
"/userdata <view/list/delete/restore/pin> <username> [version_uuid]")
|
||||
"/userdata <view/list/delete/restore/pin/dump> <username> [version_uuid]")
|
||||
.ifPresent(player::sendMessage);
|
||||
return;
|
||||
}
|
||||
@@ -199,6 +202,7 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
||||
.ifPresent(player::sendMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
final String username = args[1];
|
||||
try {
|
||||
final UUID versionUuid = UUID.fromString(args[2]);
|
||||
@@ -233,6 +237,45 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
||||
.ifPresent(player::sendMessage);
|
||||
}
|
||||
}
|
||||
case "dump" -> {
|
||||
if (!player.hasPermission(Permission.COMMAND_USER_DATA_DUMP.node)) {
|
||||
plugin.getLocales().getLocale("error_no_permission").ifPresent(player::sendMessage);
|
||||
return;
|
||||
}
|
||||
if (args.length < 3) {
|
||||
plugin.getLocales().getLocale("error_invalid_syntax",
|
||||
"/userdata dump <username> <version_uuid>")
|
||||
.ifPresent(player::sendMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean toWeb = args.length > 3 && args[3].equalsIgnoreCase("web");
|
||||
final String username = args[1];
|
||||
try {
|
||||
final UUID versionUuid = UUID.fromString(args[2]);
|
||||
CompletableFuture.runAsync(() -> plugin.getDatabase().getUserByName(username.toLowerCase()).thenAccept(
|
||||
optionalUser -> optionalUser.ifPresentOrElse(
|
||||
user -> plugin.getDatabase().getUserData(user, versionUuid).thenAccept(
|
||||
optionalUserData -> optionalUserData.ifPresentOrElse(userData -> {
|
||||
try {
|
||||
final DataDumper dumper = DataDumper.create(userData, user, plugin);
|
||||
final String result = toWeb ? dumper.toWeb() : dumper.toFile();
|
||||
plugin.getLocales().getLocale("data_dumped", versionUuid.toString()
|
||||
.split("-")[0], user.username, result)
|
||||
.ifPresent(player::sendMessage);
|
||||
} catch (IOException e) {
|
||||
plugin.getLoggingAdapter().log(Level.SEVERE, "Failed to dump user data", e);
|
||||
}
|
||||
}, () -> plugin.getLocales().getLocale("error_invalid_version_uuid")
|
||||
.ifPresent(player::sendMessage))),
|
||||
() -> plugin.getLocales().getLocale("error_invalid_player")
|
||||
.ifPresent(player::sendMessage))));
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.getLocales().getLocale("error_invalid_syntax",
|
||||
"/userdata dump <username> <version_uuid>")
|
||||
.ifPresent(player::sendMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -128,6 +128,11 @@ public class DataEditor {
|
||||
dataOwner.username, userData.versionUUID().toString())
|
||||
.ifPresent(user::sendMessage);
|
||||
}
|
||||
if (user.hasPermission(Permission.COMMAND_USER_DATA_DUMP.node)) {
|
||||
locales.getLocale("data_manager_system_buttons",
|
||||
dataOwner.username, userData.versionUUID().toString())
|
||||
.ifPresent(user::sendMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
package net.william278.husksync.util;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import net.william278.husksync.HuskSync;
|
||||
import net.william278.husksync.data.UserDataSnapshot;
|
||||
import net.william278.husksync.player.User;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Utility class for dumping {@link UserDataSnapshot}s to a file
|
||||
*/
|
||||
public class DataDumper {
|
||||
|
||||
private static final String LOGS_SITE_ENDPOINT = "https://api.mclo.gs/1/log";
|
||||
|
||||
private final HuskSync plugin;
|
||||
private final UserDataSnapshot dataSnapshot;
|
||||
private final User user;
|
||||
|
||||
private DataDumper(@NotNull UserDataSnapshot dataSnapshot,
|
||||
@NotNull User user, @NotNull HuskSync implementor) {
|
||||
this.dataSnapshot = dataSnapshot;
|
||||
this.user = user;
|
||||
this.plugin = implementor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link DataDumper} of the given {@link UserDataSnapshot}
|
||||
*
|
||||
* @param dataSnapshot The {@link UserDataSnapshot} to dump
|
||||
* @param user The {@link User} whose data is being dumped
|
||||
* @param plugin The implementing {@link HuskSync} plugin
|
||||
* @return A {@link DataDumper} for the given {@link UserDataSnapshot}
|
||||
*/
|
||||
public static DataDumper create(@NotNull UserDataSnapshot dataSnapshot,
|
||||
@NotNull User user, @NotNull HuskSync plugin) {
|
||||
return new DataDumper(dataSnapshot, user, plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the data snapshot to a string
|
||||
*
|
||||
* @return the data snapshot as a string
|
||||
*/
|
||||
@Override
|
||||
@NotNull
|
||||
public String toString() {
|
||||
return plugin.getDataAdapter().toJson(dataSnapshot.userData(), true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toWeb() {
|
||||
try {
|
||||
final URL url = new URL(LOGS_SITE_ENDPOINT);
|
||||
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setDoOutput(true);
|
||||
|
||||
// Dispatch the request
|
||||
final byte[] messageBody = getWebContentField().getBytes(StandardCharsets.UTF_8);
|
||||
final int messageLength = messageBody.length;
|
||||
connection.setFixedLengthStreamingMode(messageLength);
|
||||
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
|
||||
connection.connect();
|
||||
try (OutputStream messageOutputStream = connection.getOutputStream()) {
|
||||
messageOutputStream.write(messageBody);
|
||||
}
|
||||
|
||||
// Get the response
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||
// Get the body as a json
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
|
||||
final StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
|
||||
// Parse the response as json
|
||||
final JsonObject responseJson = JsonParser.parseString(response.toString()).getAsJsonObject();
|
||||
if (responseJson.has("url")) {
|
||||
return responseJson.get("url").getAsString();
|
||||
}
|
||||
return "(Failed to get URL from response)";
|
||||
}
|
||||
} else {
|
||||
return "(Failed to upload to logs site, got: " + connection.getResponseCode() + ")";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.getLoggingAdapter().log(Level.SEVERE, "Failed to upload data to logs site", e);
|
||||
}
|
||||
return "(Failed to upload to logs site)";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String getWebContentField() {
|
||||
return "content=" + URLEncoder.encode(toString(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the {@link UserDataSnapshot} to a file and return the file name
|
||||
*
|
||||
* @return the relative path of the file the data was dumped to
|
||||
*/
|
||||
@NotNull
|
||||
public String toFile() throws IOException {
|
||||
final File filePath = getFilePath();
|
||||
|
||||
// Write the data from #getString to the file using a writer
|
||||
try (final FileWriter writer = new FileWriter(filePath, StandardCharsets.UTF_8, false)) {
|
||||
writer.write(toString());
|
||||
} catch (IOException e) {
|
||||
throw new IOException("Failed to write data to file", e);
|
||||
}
|
||||
|
||||
return "~/plugins/HuskSync/dumps/" + filePath.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file path to dump the data to
|
||||
*
|
||||
* @return the file path
|
||||
* @throws IOException if the prerequisite dumps parent folder could not be created
|
||||
*/
|
||||
@NotNull
|
||||
private File getFilePath() throws IOException {
|
||||
return new File(getDumpsFolder(), getFileName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the folder to dump the data to and create it if it does not exist
|
||||
*
|
||||
* @return the dumps folder
|
||||
* @throws IOException if the folder could not be created
|
||||
*/
|
||||
@NotNull
|
||||
private File getDumpsFolder() throws IOException {
|
||||
final File dumpsFolder = new File(plugin.getDataFolder(), "dumps");
|
||||
if (!dumpsFolder.exists()) {
|
||||
if (!dumpsFolder.mkdirs()) {
|
||||
throw new IOException("Failed to create user data dumps folder");
|
||||
}
|
||||
}
|
||||
return dumpsFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the file to dump the data snapshot to
|
||||
*
|
||||
* @return the file name
|
||||
*/
|
||||
@NotNull
|
||||
private String getFileName() {
|
||||
return new StringJoiner("_")
|
||||
.add(user.username)
|
||||
.add(new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(dataSnapshot.versionTimestamp()))
|
||||
.add(dataSnapshot.cause().name().toLowerCase())
|
||||
.add(dataSnapshot.versionUUID().toString().split("-")[0])
|
||||
+ ".json";
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user