Implement system for shorter editor urls & display diff when changes are applied

This commit is contained in:
Luck 2017-11-22 20:54:58 +00:00
parent 26feeb125e
commit 3afeb30795
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
16 changed files with 386 additions and 252 deletions

View File

@ -31,7 +31,6 @@ user-invalid-entry: "&4{0}&c is not a valid username/uuid."
group-invalid-entry: "&4{0}&c is not a valid group name."
track-invalid-entry: "&4{0}&c is not a valid track name."
server-world-invalid-entry: "&cServer/world names can only contain alphanumeric characters and cannot exceed 36 characters in length."
use-inherit-command: "&cUse the 'parent add' and 'parent remove' commands instead of specifying the node."
verbose-invalid-filter: "&4{0}&c is not a valid verbose filter."
verbose-on: "&bVerbose logging &aenabled &bfor checks matching &aANY&b."
verbose-on-query: "&bVerbose logging &aenabled &bfor checks matching &a{0}&b."
@ -57,7 +56,10 @@ apply-edits-target-group-not-exists: "&cTarget group &4{0}&c does not exist."
apply-edits-target-user-not-uuid: "&cTarget user &4{0}&c is not a valid uuid."
apply-edits-target-user-unable-to-load: "&cUnable to load target user &4{0}&c."
apply-edits-target-unknown: "&cInvalid target. &7({0})"
apply-edits-success: "&aSuccessfully applied &b{0}&a nodes to &b{1}&a."
apply-edits-success: "&aWeb editor data was applied to &b{0}&a successfully."
apply-edits-success-summary: "&7(&a{0} &7{1} and &c{2} &7{3})"
apply-edits-diff-added: "&a+ &f{0}"
apply-edits-diff-removed: "&c- &f{0}"
editor-upload-failure: "&cUnable to upload permission data to the editor."
editor-url: "&aEditor URL:"
check-result: "&aPermission check result on user &b{0}&a for permission &b{1}&a: &f{2}"
@ -234,6 +236,11 @@ group-info-general: >
{PREFIX}&f- &3Suffixes: &a{6}\n
{PREFIX}&f- &3Meta: &a{7}
group-set-weight: "&aSet weight to &b{0}&a for group &b{1}&a."
group-set-display-name-doesnt-have: "&b{0}&a doesn't have a display name set."
group-set-display-name-already-has: "&b{0}&a already has a display name of &b{1}&a."
group-set-display-name-already-in-use: "&aThe display name &b{0}&a is already being used by &b{1}&a."
group-set-display-name: "&aSet display name to &b{0}&a for group &b{1}&a."
group-set-display-name-removed: "&aRemoved display name for group &b{0}&a."
track-info: >
{PREFIX}&b&l> &bShowing Track: &f{0}\n
{PREFIX}&f- &7Path: &f{1}

View File

@ -362,6 +362,7 @@ public class CommandManager {
case "e":
args.remove(2);
args.add(2, "editor");
break;
// Provide backwards compatibility
case "setprimarygroup":

View File

@ -25,13 +25,10 @@
package me.lucko.luckperms.common.commands.impl.generic.other;
import com.google.common.base.Splitter;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.stream.JsonWriter;
import me.lucko.luckperms.common.commands.ArgumentPermissions;
import me.lucko.luckperms.common.commands.CommandException;
@ -40,16 +37,15 @@ import me.lucko.luckperms.common.commands.abstraction.SubCommand;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.contexts.ContextSetJsonSerializer;
import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.NodeModel;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.webeditor.WebEditorUtils;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
@ -57,21 +53,9 @@ import net.kyori.text.event.ClickEvent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.format.TextColor;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.stream.Stream;
public class HolderEditor<T extends PermissionHolder> extends SubCommand<T> {
private static final String USER_ID_PATTERN = "user/";
private static final String GROUP_ID_PATTERN = "group/";
private static final String FILE_NAME = "luckperms-data.json";
public HolderEditor(LocaleManager locale, boolean user) {
super(CommandSpec.HOLDER_EDITOR.spec(locale), "editor", user ? CommandPermission.USER_EDITOR : CommandPermission.GROUP_EDITOR, Predicates.alwaysFalse());
}
@ -85,7 +69,7 @@ public class HolderEditor<T extends PermissionHolder> extends SubCommand<T> {
// form the payload data
JsonObject payload = new JsonObject();
payload.addProperty("who", id(holder));
payload.addProperty("who", WebEditorUtils.getHolderIdentifier(holder));
payload.addProperty("whoFriendly", holder.getFriendlyName());
if (holder instanceof User) {
payload.addProperty("whoUuid", ((User) holder).getUuid().toString());
@ -96,7 +80,7 @@ public class HolderEditor<T extends PermissionHolder> extends SubCommand<T> {
payload.addProperty("time", System.currentTimeMillis());
// attach the holders permissions
payload.add("nodes", serializePermissions(holder.getEnduringNodes().values().stream().map(NodeModel::fromNode)));
payload.add("nodes", WebEditorUtils.serializePermissions(holder.getEnduringNodes().values().stream().map(NodeModel::fromNode)));
// attach an array of all permissions known to the server, to use for tab completion in the editor
JsonArray knownPermsArray = new JsonArray();
@ -106,18 +90,14 @@ public class HolderEditor<T extends PermissionHolder> extends SubCommand<T> {
payload.add("knownPermissions", knownPermsArray);
// upload the payload data to gist
String dataUrl = paste(new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create().toJson(payload));
if (dataUrl == null) {
String gistId = WebEditorUtils.postToGist(new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create().toJson(payload));
if (gistId == null) {
Message.EDITOR_UPLOAD_FAILURE.send(sender);
return CommandResult.STATE_ERROR;
}
// extract the information we need from the gist url
List<String> parts = Splitter.on('/').splitToList(dataUrl);
String id = "?" + parts.get(4) + "/" + parts.get(6);
// form a url for the editor
String url = plugin.getConfiguration().get(ConfigKeys.WEB_EDITOR_URL_PATTERN) + id;
String url = plugin.getConfiguration().get(ConfigKeys.WEB_EDITOR_URL_PATTERN) + "?" + gistId;
Message.EDITOR_URL.send(sender);
@ -130,89 +110,4 @@ public class HolderEditor<T extends PermissionHolder> extends SubCommand<T> {
return CommandResult.SUCCESS;
}
private static String id(PermissionHolder holder) {
if (holder instanceof User) {
User user = ((User) holder);
return USER_ID_PATTERN + user.getUuid().toString();
} else {
Group group = ((Group) holder);
return GROUP_ID_PATTERN + group.getName();
}
}
private static String paste(String content) {
HttpURLConnection connection = null;
try {
connection = (HttpURLConnection) new URL("https://api.github.com/gists").openConnection();
connection.setRequestMethod("POST");
connection.setDoInput(true);
connection.setDoOutput(true);
try (OutputStream os = connection.getOutputStream()) {
StringWriter sw = new StringWriter();
new JsonWriter(sw).beginObject()
.name("description").value("LuckPerms Web Permissions Editor Data")
.name("public").value(false)
.name("files")
.beginObject().name(FILE_NAME)
.beginObject().name("content").value(content)
.endObject()
.endObject()
.endObject();
os.write(sw.toString().getBytes(StandardCharsets.UTF_8));
}
if (connection.getResponseCode() >= 400) {
throw new RuntimeException("Connection returned response code: " + connection.getResponseCode() + " - " + connection.getResponseMessage());
}
try (InputStream inputStream = connection.getInputStream()) {
try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
JsonObject response = new Gson().fromJson(reader, JsonObject.class);
return response.get("files").getAsJsonObject().get(FILE_NAME).getAsJsonObject().get("raw_url").getAsString();
}
}
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (connection != null) {
try {
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private static JsonArray serializePermissions(Stream<NodeModel> nodes) {
JsonArray arr = new JsonArray();
nodes.forEach(node -> {
JsonObject attributes = new JsonObject();
attributes.addProperty("permission", node.getPermission());
attributes.addProperty("value", node.getValue());
if (!node.getServer().equals("global")) {
attributes.addProperty("server", node.getServer());
}
if (!node.getWorld().equals("global")) {
attributes.addProperty("world", node.getWorld());
}
if (node.getExpiry() != 0L) {
attributes.addProperty("expiry", node.getExpiry());
}
if (!node.getContexts().isEmpty()) {
attributes.add("context", ContextSetJsonSerializer.serializeContextSet(node.getContexts()));
}
arr.add(attributes);
});
return arr;
}
}

View File

@ -85,6 +85,7 @@ public class ParentInfo extends SharedSubCommand {
List<Node> page = new ArrayList<>();
for (Node node : nodes) {
if (!node.isGroupNode()) continue;
if (!node.getValuePrimitive()) continue;
if (node.isTemporary()) continue;
page.add(node);
}
@ -105,6 +106,7 @@ public class ParentInfo extends SharedSubCommand {
List<Node> page = new ArrayList<>();
for (Node node : nodes) {
if (!node.isGroupNode()) continue;
if (!node.getValuePrimitive()) continue;
if (node.isPermanent()) continue;
page.add(node);
}

View File

@ -121,14 +121,10 @@ public class PermissionInfo extends SharedSubCommand {
context:
if (index != -1) {
String key = filter.substring(0, index);
if (key.equals("")) {
break context;
}
if (key.equals("")) break context;
String value = filter.substring(index + 1);
if (value.equals("")) {
break context;
}
if (value.equals("")) break context;
contextFilter = Maps.immutableEntry(key, value);
}
@ -140,7 +136,9 @@ public class PermissionInfo extends SharedSubCommand {
List<Node> l = new ArrayList<>();
for (Node node : nodes) {
if (node.isGroupNode() || node.isPrefix() || node.isSuffix() || node.isMeta()) continue;
if ((node.isGroupNode() && node.getValuePrimitive()) || node.isPrefix() || node.isSuffix() || node.isMeta()) continue;
// check against filters
if (nodeFilter != null && !node.getPermission().startsWith(nodeFilter)) continue;
if (contextFilter != null && !node.getFullContexts().hasIgnoreCase(contextFilter.getKey(), contextFilter.getValue())) continue;
if (temp != node.isTemporary()) continue;

View File

@ -62,7 +62,7 @@ public class PermissionSet extends SharedSubCommand {
}
String node = ArgumentUtils.handleString(0, args);
boolean b = ArgumentUtils.handleBoolean(1, args);
boolean value = ArgumentUtils.handleBoolean(1, args);
MutableContextSet context = ArgumentUtils.handleContext(2, args, plugin);
if (ArgumentPermissions.checkContext(plugin, sender, permission, context)) {
@ -75,13 +75,13 @@ public class PermissionSet extends SharedSubCommand {
return CommandResult.NO_PERMISSION;
}
DataMutateResult result = holder.setPermission(NodeFactory.newBuilder(node).setValue(b).withExtraContext(context).build());
DataMutateResult result = holder.setPermission(NodeFactory.newBuilder(node).setValue(value).withExtraContext(context).build());
if (result.asBoolean()) {
Message.SETPERMISSION_SUCCESS.send(sender, node, b, holder.getFriendlyName(), CommandUtils.contextSetToString(context));
Message.SETPERMISSION_SUCCESS.send(sender, node, value, holder.getFriendlyName(), CommandUtils.contextSetToString(context));
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("permission", "set", node, b, context)
.action("permission", "set", node, value, context)
.build().submit(plugin, sender);
save(holder, sender, plugin);

View File

@ -67,7 +67,7 @@ public class PermissionSetTemp extends SharedSubCommand {
}
String node = ArgumentUtils.handleString(0, args);
boolean b = ArgumentUtils.handleBoolean(1, args);
boolean value = ArgumentUtils.handleBoolean(1, args);
long duration = ArgumentUtils.handleDuration(2, args);
MutableContextSet context = ArgumentUtils.handleContext(3, args, plugin);
@ -82,14 +82,14 @@ public class PermissionSetTemp extends SharedSubCommand {
}
TemporaryModifier modifier = plugin.getConfiguration().get(ConfigKeys.TEMPORARY_ADD_BEHAVIOUR);
Map.Entry<DataMutateResult, Node> result = holder.setPermission(NodeFactory.newBuilder(node).setValue(b).withExtraContext(context).setExpiry(duration).build(), modifier);
Map.Entry<DataMutateResult, Node> result = holder.setPermission(NodeFactory.newBuilder(node).setValue(value).withExtraContext(context).setExpiry(duration).build(), modifier);
if (result.getKey().asBoolean()) {
duration = result.getValue().getExpiryUnixTime();
Message.SETPERMISSION_TEMP_SUCCESS.send(sender, node, b, holder.getFriendlyName(), DateUtil.formatDateDiff(duration), CommandUtils.contextSetToString(context));
Message.SETPERMISSION_TEMP_SUCCESS.send(sender, node, value, holder.getFriendlyName(), DateUtil.formatDateDiff(duration), CommandUtils.contextSetToString(context));
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("permission", "settemp", node, b, duration, context)
.action("permission", "settemp", node, value, duration, context)
.build().submit(plugin, sender);
save(holder, sender, plugin);

View File

@ -52,7 +52,7 @@ public class LogGroupHistory extends SubCommand<Log> {
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Log log, List<String> args, String label) throws CommandException {
String group = args.get(0).toLowerCase();
int page = -999;
int page = Integer.MIN_VALUE;
if (args.size() == 2) {
try {
@ -75,7 +75,7 @@ public class LogGroupHistory extends SubCommand<Log> {
return CommandResult.STATE_ERROR;
}
if (page == -999) {
if (page == Integer.MIN_VALUE) {
page = maxPage;
}

View File

@ -51,7 +51,7 @@ public class LogSearch extends SubCommand<Log> {
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Log log, List<String> args, String label) throws CommandException {
int page = -999;
int page = Integer.MIN_VALUE;
if (args.size() > 1) {
try {
page = Integer.parseInt(args.get(args.size() - 1));
@ -68,7 +68,7 @@ public class LogSearch extends SubCommand<Log> {
return CommandResult.STATE_ERROR;
}
if (page == -999) {
if (page == Integer.MIN_VALUE) {
page = maxPage;
}

View File

@ -52,7 +52,7 @@ public class LogTrackHistory extends SubCommand<Log> {
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Log log, List<String> args, String label) throws CommandException {
String track = args.get(0).toLowerCase();
int page = -999;
int page = Integer.MIN_VALUE;
if (args.size() == 2) {
try {
@ -75,7 +75,7 @@ public class LogTrackHistory extends SubCommand<Log> {
return CommandResult.STATE_ERROR;
}
if (page == -999) {
if (page == Integer.MIN_VALUE) {
page = maxPage;
}

View File

@ -55,7 +55,7 @@ public class LogUserHistory extends SubCommand<Log> {
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Log log, List<String> args, String label) throws CommandException {
String target = args.get(0);
int page = -999;
int page = Integer.MIN_VALUE;
if (args.size() == 2) {
try {
@ -95,7 +95,7 @@ public class LogUserHistory extends SubCommand<Log> {
}
}
if (page == -999) {
if (page == Integer.MIN_VALUE) {
page = log.getUserHistoryMaxPages(uuid);
}

View File

@ -25,13 +25,10 @@
package me.lucko.luckperms.common.commands.impl.misc;
import com.google.common.base.Splitter;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.common.collect.Maps;
import com.google.gson.JsonObject;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.commands.ArgumentPermissions;
import me.lucko.luckperms.common.commands.CommandException;
@ -41,25 +38,20 @@ import me.lucko.luckperms.common.commands.abstraction.SingleCommand;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.CommandUtils;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.contexts.ContextSetJsonSerializer;
import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.node.NodeModel;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.DateUtil;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.webeditor.WebEditorUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
public class ApplyEditsCommand extends SingleCommand {
@ -72,27 +64,13 @@ public class ApplyEditsCommand extends SingleCommand {
String code = args.get(0);
String who = args.size() == 2 ? args.get(1) : null;
if (!code.contains("/")) {
if (code.isEmpty()) {
Message.APPLY_EDITS_INVALID_CODE.send(sender, code);
return CommandResult.INVALID_ARGS;
}
Iterator<String> codeParts = Splitter.on('/').limit(2).split(code).iterator();
String part1 = codeParts.next();
String part2 = codeParts.next();
if (part1.isEmpty() || part2.isEmpty()) {
Message.APPLY_EDITS_INVALID_CODE.send(sender, code);
return CommandResult.INVALID_ARGS;
}
String url = "https://gist.githubusercontent.com/anonymous/" + part1 + "/raw/" + part2 + "/luckperms-data.json";
JsonObject data;
try {
data = read(url);
} catch (Exception e) {
e.printStackTrace();
JsonObject data = WebEditorUtils.getDataFromGist(code);
if (data == null) {
Message.APPLY_EDITS_UNABLE_TO_READ.send(sender, code);
return CommandResult.FAILURE;
}
@ -106,34 +84,9 @@ public class ApplyEditsCommand extends SingleCommand {
who = data.get("who").getAsString();
}
PermissionHolder holder;
if (who.startsWith("group/")) {
String group = who.substring("group/".length());
holder = plugin.getGroupManager().getIfLoaded(group);
if (holder == null) {
Message.APPLY_EDITS_TARGET_GROUP_NOT_EXISTS.send(sender, group);
return CommandResult.STATE_ERROR;
}
} else if (who.startsWith("user/")) {
String user = who.substring("user/".length());
UUID uuid = CommandUtils.parseUuid(user);
if (uuid == null) {
Message.APPLY_EDITS_TARGET_USER_NOT_UUID.send(sender, user);
return CommandResult.STATE_ERROR;
}
holder = plugin.getUserManager().getIfLoaded(uuid);
if (holder == null) {
plugin.getStorage().loadUser(uuid, null).join();
}
holder = plugin.getUserManager().getIfLoaded(uuid);
if (holder == null) {
Message.APPLY_EDITS_TARGET_USER_UNABLE_TO_LOAD.send(sender, uuid.toString());
return CommandResult.STATE_ERROR;
}
} else {
Message.APPLY_EDITS_TARGET_UNKNOWN.send(sender, who);
PermissionHolder holder = WebEditorUtils.getHolderFromIdentifier(plugin, sender, who);
if (holder == null) {
// the #getHolderFromIdentifier method will send the error message onto the sender
return CommandResult.STATE_ERROR;
}
@ -143,66 +96,54 @@ public class ApplyEditsCommand extends SingleCommand {
}
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("applyedits", part1 + "/" + part2)
.action("applyedits", code)
.build().submit(plugin, sender);
Set<NodeModel> nodes = deserializePermissions(data.getAsJsonArray("nodes"));
holder.setEnduringNodes(nodes.stream().map(NodeModel::toNode).collect(Collectors.toSet()));
Message.APPLY_EDITS_SUCCESS.send(sender, nodes.size(), holder.getFriendlyName());
Set<NodeModel> rawNodes = WebEditorUtils.deserializePermissions(data.getAsJsonArray("nodes"));
Set<Node> before = new HashSet<>(holder.getEnduringNodes().values());
Set<Node> nodes = rawNodes.stream().map(NodeModel::toNode).collect(Collectors.toSet());
holder.setEnduringNodes(nodes);
Map.Entry<Set<Node>, Set<Node>> diff = diff(before, nodes);
int additions = diff.getKey().size();
int deletions = diff.getValue().size();
String additionsSummary = "addition" + (additions == 1 ? "" : "s");
String deletionsSummary = "deletion" + (deletions == 1 ? "" : "s");
Message.APPLY_EDITS_SUCCESS.send(sender, holder.getFriendlyName());
Message.APPLY_EDITS_SUCCESS_SUMMARY.send(sender, additions, additionsSummary, deletions, deletionsSummary);
for (Node n : diff.getKey()) {
Message.APPLY_EDITS_DIFF_ADDED.send(sender, formatNode(n));
}
for (Node n : diff.getValue()) {
Message.APPLY_EDITS_DIFF_REMOVED.send(sender, formatNode(n));
}
SharedSubCommand.save(holder, sender, plugin);
return CommandResult.SUCCESS;
}
private static String formatNode(Node n) {
return n.getPermission() + " &7(" + (n.getValuePrimitive() ? "&a" : "&c") + n.getValuePrimitive() + "&7)" + CommandUtils.getAppendableNodeContextString(n) +
(n.isTemporary() ? " &7(" + DateUtil.formatDateDiffShort(n.getExpiryUnixTime()) + ")" : "");
}
private static Map.Entry<Set<Node>, Set<Node>> diff(Set<Node> before, Set<Node> after) {
// entries in before but not after are being removed
// entries in after but not before are being added
Set<Node> added = new HashSet<>(after);
added.removeAll(before);
Set<Node> removed = new HashSet<>(before);
removed.removeAll(after);
return Maps.immutableEntry(added, removed);
}
@Override
public boolean shouldDisplay() {
return false;
}
private static JsonObject read(String address) throws IOException {
URL url = new URL(address);
try (InputStream in = url.openStream(); InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
return new Gson().fromJson(reader, JsonObject.class);
}
}
private static Set<NodeModel> deserializePermissions(JsonArray permissionsSection) {
Set<NodeModel> nodes = new HashSet<>();
for (JsonElement ent : permissionsSection) {
if (!ent.isJsonObject()) {
continue;
}
JsonObject data = ent.getAsJsonObject();
String permission = data.get("permission").getAsString();
boolean value = true;
String server = "global";
String world = "global";
long expiry = 0L;
ImmutableContextSet context = ImmutableContextSet.empty();
if (data.has("value")) {
value = data.get("value").getAsBoolean();
}
if (data.has("server")) {
server = data.get("server").getAsString();
}
if (data.has("world")) {
world = data.get("world").getAsString();
}
if (data.has("expiry")) {
expiry = data.get("expiry").getAsLong();
}
if (data.has("context") && data.get("context").isJsonObject()) {
JsonObject contexts = data.get("context").getAsJsonObject();
context = ContextSetJsonSerializer.deserializeContextSet(contexts).makeImmutable();
}
nodes.add(NodeModel.of(permission, value, server, world, expiry, context));
}
return nodes;
}
}

View File

@ -125,7 +125,10 @@ public enum Message {
APPLY_EDITS_TARGET_USER_NOT_UUID("&cTarget user &4{}&c is not a valid uuid.", true),
APPLY_EDITS_TARGET_USER_UNABLE_TO_LOAD("&cUnable to load target user &4{}&c.", true),
APPLY_EDITS_TARGET_UNKNOWN("&cInvalid target. &7({})", true),
APPLY_EDITS_SUCCESS("&aSuccessfully applied &b{}&a nodes to &b{}&a.", true),
APPLY_EDITS_SUCCESS("&aWeb editor data was applied to &b{}&a successfully.", true),
APPLY_EDITS_SUCCESS_SUMMARY("&7(&a{} &7{} and &c{} &7{})", true),
APPLY_EDITS_DIFF_ADDED("&a+ &f{}", false),
APPLY_EDITS_DIFF_REMOVED("&c- &f{}", false),
EDITOR_UPLOAD_FAILURE("&cUnable to upload permission data to the editor.", true),
EDITOR_URL("&aEditor URL:", true),

View File

@ -165,8 +165,13 @@ public class DateUtil {
return DateUtil.formatDateDiff(now, then);
}
public static String formatDateDiffShort(long seconds) {
long now = unixSecondsNow();
return formatTimeShort(seconds - now);
}
public static String formatTimeShort(long seconds) {
if (seconds == 0) {
if (seconds <= 0) {
return "0s";
}

View File

@ -29,6 +29,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.stream.JsonWriter;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
@ -57,8 +58,8 @@ public class PasteUtils {
HttpURLConnection connection = null;
try {
connection = (HttpURLConnection) new URL(GIST_API).openConnection();
connection.setRequestProperty("User-Agent", "luckperms");
connection.setRequestMethod("POST");
connection.setDoInput(true);
connection.setDoOutput(true);
try (OutputStream os = connection.getOutputStream()) {
@ -85,9 +86,11 @@ public class PasteUtils {
String pasteUrl;
try (InputStream inputStream = connection.getInputStream()) {
try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
JsonObject response = new Gson().fromJson(reader, JsonObject.class);
pasteUrl = response.get("html_url").getAsString();
try (InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
try (BufferedReader reader = new BufferedReader(inputStreamReader)) {
JsonObject response = new Gson().fromJson(reader, JsonObject.class);
pasteUrl = response.get("html_url").getAsString();
}
}
}
@ -95,7 +98,9 @@ public class PasteUtils {
try {
connection = (HttpURLConnection) new URL(SHORTEN_API).openConnection();
connection.setRequestProperty("User-Agent", "luckperms");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestMethod("GET");
connection.setDoOutput(true);
try (OutputStream os = connection.getOutputStream()) {
os.write(("url=" + pasteUrl).getBytes(StandardCharsets.UTF_8));

View File

@ -0,0 +1,277 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.webeditor;
import lombok.experimental.UtilityClass;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.stream.JsonWriter;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.CommandUtils;
import me.lucko.luckperms.common.contexts.ContextSetJsonSerializer;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.NodeModel;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Stream;
/**
* Utility methods for interacting with the LuckPerms web permission editor.
*/
@UtilityClass
public class WebEditorUtils {
private static final String FILE_NAME = "luckperms-data.json";
private static final String GIST_API_URL = "https://api.github.com/gists";
private static final String USER_ID_PATTERN = "user/";
private static final String GROUP_ID_PATTERN = "group/";
public static String getHolderIdentifier(PermissionHolder holder) {
if (holder instanceof User) {
User user = ((User) holder);
return USER_ID_PATTERN + user.getUuid().toString();
} else {
Group group = ((Group) holder);
return GROUP_ID_PATTERN + group.getName();
}
}
public static PermissionHolder getHolderFromIdentifier(LuckPermsPlugin plugin, Sender sender, String who) {
if (who.startsWith("group/")) {
String group = who.substring("group/".length());
Group holder = plugin.getGroupManager().getIfLoaded(group);
if (holder == null) {
Message.APPLY_EDITS_TARGET_GROUP_NOT_EXISTS.send(sender, group);
}
return holder;
} else if (who.startsWith("user/")) {
String user = who.substring("user/".length());
UUID uuid = CommandUtils.parseUuid(user);
if (uuid == null) {
Message.APPLY_EDITS_TARGET_USER_NOT_UUID.send(sender, user);
return null;
}
plugin.getStorage().loadUser(uuid, null).join();
User holder = plugin.getUserManager().getIfLoaded(uuid);
if (holder == null) {
Message.APPLY_EDITS_TARGET_USER_UNABLE_TO_LOAD.send(sender, uuid.toString());
}
return holder;
} else {
Message.APPLY_EDITS_TARGET_UNKNOWN.send(sender, who);
return null;
}
}
public static String postToGist(String content) {
HttpURLConnection connection = null;
try {
connection = (HttpURLConnection) new URL(GIST_API_URL).openConnection();
connection.addRequestProperty("User-Agent", "luckperms");
connection.setRequestMethod("POST");
connection.setDoOutput(true);
try (OutputStream os = connection.getOutputStream()) {
StringWriter sw = new StringWriter();
new JsonWriter(sw).beginObject()
.name("description").value("LuckPerms Web Permissions Editor Data")
.name("public").value(false)
.name("files")
.beginObject().name(FILE_NAME)
.beginObject().name("content").value(content)
.endObject()
.endObject()
.endObject();
os.write(sw.toString().getBytes(StandardCharsets.UTF_8));
}
if (connection.getResponseCode() >= 400) {
throw new RuntimeException("Connection returned response code: " + connection.getResponseCode() + " - " + connection.getResponseMessage());
}
try (InputStream inputStream = connection.getInputStream()) {
try (InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
try (BufferedReader reader = new BufferedReader(inputStreamReader)) {
JsonObject response = new Gson().fromJson(reader, JsonObject.class);
return response.get("id").getAsString();
}
}
}
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (connection != null) {
try {
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public static JsonObject getDataFromGist(String id) {
HttpURLConnection connection = null;
try {
connection = (HttpURLConnection) new URL(GIST_API_URL + "/" + id).openConnection();
connection.addRequestProperty("User-Agent", "luckperms");
connection.setRequestMethod("GET");
if (connection.getResponseCode() >= 400) {
throw new RuntimeException("Connection returned response code: " + connection.getResponseCode() + " - " + connection.getResponseMessage());
}
try (InputStream inputStream = connection.getInputStream()) {
try (InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
try (BufferedReader reader = new BufferedReader(inputStreamReader)) {
JsonObject response = new Gson().fromJson(reader, JsonObject.class);
JsonObject files = response.get("files").getAsJsonObject();
JsonObject permsFile = files.get(FILE_NAME).getAsJsonObject();
// uh..
if (permsFile.get("truncated").getAsBoolean()) {
String rawUrlStr = permsFile.get("raw_url").getAsString();
URL rawUrl = new URL(rawUrlStr);
try (InputStream rawInputStream = rawUrl.openStream()) {
try (InputStreamReader rawInputStreamReader = new InputStreamReader(rawInputStream, StandardCharsets.UTF_8)) {
try (BufferedReader rawReader = new BufferedReader(rawInputStreamReader)) {
return new Gson().fromJson(rawReader, JsonObject.class);
}
}
}
} else {
String content = permsFile.get("content").getAsString();
return new Gson().fromJson(content, JsonObject.class);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (connection != null) {
try {
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public static JsonArray serializePermissions(Stream<NodeModel> nodes) {
JsonArray arr = new JsonArray();
nodes.forEach(node -> {
JsonObject attributes = new JsonObject();
attributes.addProperty("permission", node.getPermission());
attributes.addProperty("value", node.getValue());
if (!node.getServer().equals("global")) {
attributes.addProperty("server", node.getServer());
}
if (!node.getWorld().equals("global")) {
attributes.addProperty("world", node.getWorld());
}
if (node.getExpiry() != 0L) {
attributes.addProperty("expiry", node.getExpiry());
}
if (!node.getContexts().isEmpty()) {
attributes.add("context", ContextSetJsonSerializer.serializeContextSet(node.getContexts()));
}
arr.add(attributes);
});
return arr;
}
public static Set<NodeModel> deserializePermissions(JsonArray permissionsSection) {
Set<NodeModel> nodes = new HashSet<>();
for (JsonElement ent : permissionsSection) {
if (!ent.isJsonObject()) {
continue;
}
JsonObject data = ent.getAsJsonObject();
String permission = data.get("permission").getAsString();
boolean value = true;
String server = "global";
String world = "global";
long expiry = 0L;
ImmutableContextSet context = ImmutableContextSet.empty();
if (data.has("value")) {
value = data.get("value").getAsBoolean();
}
if (data.has("server")) {
server = data.get("server").getAsString();
}
if (data.has("world")) {
world = data.get("world").getAsString();
}
if (data.has("expiry")) {
expiry = data.get("expiry").getAsLong();
}
if (data.has("context") && data.get("context").isJsonObject()) {
JsonObject contexts = data.get("context").getAsJsonObject();
context = ContextSetJsonSerializer.deserializeContextSet(contexts).makeImmutable();
}
nodes.add(NodeModel.of(permission, value, server, world, expiry, context));
}
return nodes;
}
}