Update web related functionality to point to new locations and APIs (#770) (#784)

This commit is contained in:
Luck 2018-02-22 22:09:37 +00:00 committed by GitHub
parent da9977a30f
commit 31d435dc2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 749 additions and 784 deletions

View File

@ -105,8 +105,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.annotation.Nullable;
/** /**
* LuckPerms implementation for the Bukkit API. * LuckPerms implementation for the Bukkit API.
*/ */
@ -190,7 +188,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
private void enable() { private void enable() {
this.startTime = System.currentTimeMillis(); this.startTime = System.currentTimeMillis();
sendStartupBanner(getConsoleSender()); sendStartupBanner(getConsoleSender());
this.verboseHandler = new VerboseHandler(this.scheduler.asyncBukkit(), getVersion()); this.verboseHandler = new VerboseHandler(this.scheduler.asyncBukkit());
this.permissionVault = new PermissionVault(this.scheduler.asyncBukkit()); this.permissionVault = new PermissionVault(this.scheduler.asyncBukkit());
this.logDispatcher = new LogDispatcher(this); this.logDispatcher = new LogDispatcher(this);
@ -539,14 +537,13 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
} }
} }
@Nullable
@Override @Override
public Contexts getContextForUser(User user) { public Optional<Contexts> getContextForUser(User user) {
Player player = getPlayer(user); Player player = getPlayer(user);
if (player == null) { if (player == null) {
return null; return Optional.empty();
} }
return this.contextManager.getApplicableContexts(player); return Optional.of(this.contextManager.getApplicableContexts(player));
} }
@Override @Override

View File

@ -90,8 +90,6 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.annotation.Nullable;
/** /**
* LuckPerms implementation for the BungeeCord API. * LuckPerms implementation for the BungeeCord API.
*/ */
@ -141,7 +139,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
public void onEnable() { public void onEnable() {
this.startTime = System.currentTimeMillis(); this.startTime = System.currentTimeMillis();
sendStartupBanner(getConsoleSender()); sendStartupBanner(getConsoleSender());
this.verboseHandler = new VerboseHandler(this.scheduler.async(), getVersion()); this.verboseHandler = new VerboseHandler(this.scheduler.async());
this.permissionVault = new PermissionVault(this.scheduler.async()); this.permissionVault = new PermissionVault(this.scheduler.async());
this.logDispatcher = new LogDispatcher(this); this.logDispatcher = new LogDispatcher(this);
@ -339,14 +337,13 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
return Optional.empty(); return Optional.empty();
} }
@Nullable
@Override @Override
public Contexts getContextForUser(User user) { public Optional<Contexts> getContextForUser(User user) {
ProxiedPlayer player = getPlayer(user); ProxiedPlayer player = getPlayer(user);
if (player == null) { if (player == null) {
return null; return Optional.empty();
} }
return this.contextManager.getApplicableContexts(player); return Optional.of(this.contextManager.getApplicableContexts(player));
} }
@Override @Override

View File

@ -74,14 +74,14 @@ public class ApiContextManager implements me.lucko.luckperms.api.context.Context
@Override @Override
public Optional<ImmutableContextSet> lookupApplicableContext(@Nonnull User user) { public Optional<ImmutableContextSet> lookupApplicableContext(@Nonnull User user) {
Objects.requireNonNull(user, "user"); Objects.requireNonNull(user, "user");
return Optional.ofNullable(this.plugin.getContextForUser(ApiUser.cast(user))).map(c -> c.getContexts().makeImmutable()); return this.plugin.getContextForUser(ApiUser.cast(user)).map(c -> c.getContexts().makeImmutable());
} }
@Nonnull @Nonnull
@Override @Override
public Optional<Contexts> lookupApplicableContexts(@Nonnull User user) { public Optional<Contexts> lookupApplicableContexts(@Nonnull User user) {
Objects.requireNonNull(user, "user"); Objects.requireNonNull(user, "user");
return Optional.ofNullable(this.plugin.getContextForUser(ApiUser.cast(user))); return this.plugin.getContextForUser(ApiUser.cast(user));
} }
@Nonnull @Nonnull

View File

@ -25,7 +25,6 @@
package me.lucko.luckperms.common.commands.impl.generic.other; package me.lucko.luckperms.common.commands.impl.generic.other;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import me.lucko.luckperms.common.commands.ArgumentPermissions; import me.lucko.luckperms.common.commands.ArgumentPermissions;
@ -40,6 +39,7 @@ import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.model.PermissionHolder; import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates; import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.web.StandardPastebin;
import me.lucko.luckperms.common.webeditor.WebEditor; import me.lucko.luckperms.common.webeditor.WebEditor;
import net.kyori.text.Component; import net.kyori.text.Component;
@ -69,14 +69,14 @@ public class HolderEditor<T extends PermissionHolder> extends SubCommand<T> {
JsonObject payload = WebEditor.formPayload(Collections.singletonList(holder), sender, label, plugin); JsonObject payload = WebEditor.formPayload(Collections.singletonList(holder), sender, label, plugin);
// upload the payload data to gist // upload the payload data to gist
String gistId = WebEditor.postToGist(new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create().toJson(payload)); String pasteId = StandardPastebin.BYTEBIN.postJson(payload).id();
if (gistId == null) { if (pasteId == null) {
Message.EDITOR_UPLOAD_FAILURE.send(sender); Message.EDITOR_UPLOAD_FAILURE.send(sender);
return CommandResult.STATE_ERROR; return CommandResult.STATE_ERROR;
} }
// form a url for the editor // form a url for the editor
String url = plugin.getConfiguration().get(ConfigKeys.WEB_EDITOR_URL_PATTERN) + "?" + gistId; String url = plugin.getConfiguration().get(ConfigKeys.WEB_EDITOR_URL_PATTERN) + "?" + pasteId;
Message.EDITOR_URL.send(sender); Message.EDITOR_URL.send(sender);

View File

@ -68,7 +68,7 @@ public class CheckCommand extends SingleCommand {
return CommandResult.STATE_ERROR; return CommandResult.STATE_ERROR;
} }
Tristate tristate = user.getCachedData().getPermissionData(plugin.getContextForUser(user)).getPermissionValue(permission, CheckOrigin.INTERNAL); Tristate tristate = user.getCachedData().getPermissionData(plugin.getContextForUser(user).orElse(plugin.getContextManager().getStaticContexts())).getPermissionValue(permission, CheckOrigin.INTERNAL);
Message.CHECK_RESULT.send(sender, user.getFriendlyName(), permission, CommandUtils.formatTristate(tristate)); Message.CHECK_RESULT.send(sender, user.getFriendlyName(), permission, CommandUtils.formatTristate(tristate));
return CommandResult.SUCCESS; return CommandResult.SUCCESS;
} }

View File

@ -27,9 +27,6 @@ package me.lucko.luckperms.common.commands.impl.misc;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.caching.MetaContexts; import me.lucko.luckperms.api.caching.MetaContexts;
@ -51,9 +48,11 @@ import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.processors.PermissionProcessor; import me.lucko.luckperms.common.processors.PermissionProcessor;
import me.lucko.luckperms.common.utils.Gist;
import me.lucko.luckperms.common.utils.Predicates; import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.TextUtils; import me.lucko.luckperms.common.utils.gson.JArray;
import me.lucko.luckperms.common.utils.gson.JObject;
import me.lucko.luckperms.common.utils.web.Pastebin;
import me.lucko.luckperms.common.utils.web.StandardPastebin;
import net.kyori.text.Component; import net.kyori.text.Component;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
@ -66,7 +65,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Supplier; import java.util.function.BiConsumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class DebugCommand extends SingleCommand { public class DebugCommand extends SingleCommand {
@ -80,19 +79,26 @@ public class DebugCommand extends SingleCommand {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, List<String> args, String label) { public CommandResult execute(LuckPermsPlugin plugin, Sender sender, List<String> args, String label) {
Message.DEBUG_START.send(sender); Message.DEBUG_START.send(sender);
Gist gist = Gist.builder() StringBuilder sb = new StringBuilder();
.description("LuckPerms Debug Output") sb.append("LuckPerms Debug Output\n\n\n");
.file("__DEBUG__.md", TextUtils.joinNewline("# Debug Output", "The debugging data can be found in the files below."))
.file("platform.json", GSON.toJson(getPlatformData(plugin).toJson())) BiConsumer<String, JObject> builder = (name, content) -> {
.file("storage.json", GSON.toJson(getStorageData(plugin).toJson())) sb.append("-- ").append(name).append(" --\n");
.file("context.json", GSON.toJson(getContextData(plugin).toJson())) sb.append(GSON.toJson(content.toJson()));
.file("players.json", GSON.toJson(getPlayersData(plugin).toJson())) sb.append("\n\n");
.upload(); };
builder.accept("platform.json", getPlatformData(plugin));
builder.accept("storage.json", getStorageData(plugin));
builder.accept("context.json", getContextData(plugin));
builder.accept("players.json", getPlayersData(plugin));
Pastebin.Paste paste = StandardPastebin.HASTEBIN.postPlain(sb.toString());
Message.DEBUG_URL.send(sender); Message.DEBUG_URL.send(sender);
Component message = TextComponent.builder(gist.getUrl()).color(TextColor.AQUA) Component message = TextComponent.builder(paste.url()).color(TextColor.AQUA)
.clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, String.valueOf(gist.getUrl()))) .clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, String.valueOf(paste.url())))
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to open the debugging data.").color(TextColor.GRAY))) .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to open the debugging data.").color(TextColor.GRAY)))
.build(); .build();
@ -190,7 +196,7 @@ public class DebugCommand extends SingleCommand {
) )
.add("activeContext", () -> { .add("activeContext", () -> {
JObject obj = new JObject(); JObject obj = new JObject();
Contexts contexts = plugin.getContextForUser(user); Contexts contexts = plugin.getContextForUser(user).orElse(null);
if (contexts != null) { if (contexts != null) {
MetaContexts metaContexts = plugin.getContextManager().formMetaContexts(contexts); MetaContexts metaContexts = plugin.getContextManager().formMetaContexts(contexts);
obj.add("data", new JObject() obj.add("data", new JObject()
@ -298,74 +304,4 @@ public class DebugCommand extends SingleCommand {
}); });
} }
// stupidly simply fluent gson wrappers
private interface JElement {
JsonElement toJson();
}
private static final class JObject implements JElement {
private final JsonObject o = new JsonObject();
@Override
public JsonElement toJson() {
return this.o;
}
public JObject add(String key, String value) {
this.o.addProperty(key, value);
return this;
}
public JObject add(String key, Number value) {
this.o.addProperty(key, value);
return this;
}
public JObject add(String key, Boolean value) {
this.o.addProperty(key, value);
return this;
}
public JObject add(String key, JsonElement value) {
this.o.add(key, value);
return this;
}
public JObject add(String key, JElement value) {
return add(key, value.toJson());
}
public JObject add(String key, Supplier<? extends JElement> value) {
return add(key, value.get().toJson());
}
}
private static final class JArray implements JElement {
private final JsonArray o = new JsonArray();
@Override
public JsonElement toJson() {
return this.o;
}
public JArray add(String value) {
this.o.add(value);
return this;
}
public JArray add(JsonElement value) {
this.o.add(value);
return this;
}
public JArray add(JElement value) {
return add(value.toJson());
}
public JArray add(Supplier<? extends JElement> value) {
return add(value.get().toJson());
}
}
} }

View File

@ -25,7 +25,6 @@
package me.lucko.luckperms.common.commands.impl.misc; package me.lucko.luckperms.common.commands.impl.misc;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import me.lucko.luckperms.common.commands.ArgumentPermissions; import me.lucko.luckperms.common.commands.ArgumentPermissions;
@ -41,6 +40,7 @@ import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.model.PermissionHolder; import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates; import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.web.StandardPastebin;
import me.lucko.luckperms.common.webeditor.WebEditor; import me.lucko.luckperms.common.webeditor.WebEditor;
import net.kyori.text.Component; import net.kyori.text.Component;
@ -95,14 +95,14 @@ public class EditorCommand extends SingleCommand {
JsonObject payload = WebEditor.formPayload(holders, sender, label, plugin); JsonObject payload = WebEditor.formPayload(holders, sender, label, plugin);
// upload the payload data to gist // upload the payload data to gist
String gistId = WebEditor.postToGist(new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create().toJson(payload)); String pasteId = StandardPastebin.BYTEBIN.postJson(payload).id();
if (gistId == null) { if (pasteId == null) {
Message.EDITOR_UPLOAD_FAILURE.send(sender); Message.EDITOR_UPLOAD_FAILURE.send(sender);
return CommandResult.STATE_ERROR; return CommandResult.STATE_ERROR;
} }
// form a url for the editor // form a url for the editor
String url = plugin.getConfiguration().get(ConfigKeys.WEB_EDITOR_URL_PATTERN) + "?" + gistId; String url = plugin.getConfiguration().get(ConfigKeys.WEB_EDITOR_URL_PATTERN) + "?" + pasteId;
Message.EDITOR_URL.send(sender); Message.EDITOR_URL.send(sender);

View File

@ -30,14 +30,13 @@ import me.lucko.luckperms.common.commands.CommandPermission;
import me.lucko.luckperms.common.commands.CommandResult; import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.abstraction.SingleCommand; import me.lucko.luckperms.common.commands.abstraction.SingleCommand;
import me.lucko.luckperms.common.commands.sender.Sender; import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.ArgumentUtils; import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.locale.CommandSpec; import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message; import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.treeview.TreeView; import me.lucko.luckperms.common.treeview.TreeView;
import me.lucko.luckperms.common.treeview.TreeViewBuilder;
import me.lucko.luckperms.common.utils.Predicates; import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.Uuids; import me.lucko.luckperms.common.utils.Uuids;
@ -58,21 +57,17 @@ public class TreeCommand extends SingleCommand {
@Override @Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, List<String> args, String label) { public CommandResult execute(LuckPermsPlugin plugin, Sender sender, List<String> args, String label) {
String selection = "."; String selection = ".";
int maxLevel = 5;
String player = null; String player = null;
if (!args.isEmpty()) { if (!args.isEmpty()) {
selection = args.get(0); selection = args.get(0);
} }
if (args.size() > 1) { if (args.size() > 1) {
maxLevel = ArgumentUtils.handleIntOrElse(1, args, 5); player = args.get(1);
}
if (args.size() > 2) {
player = args.get(2);
} }
if (player != null) {
User user; User user;
if (player != null) {
UUID u = Uuids.parseNullable(player); UUID u = Uuids.parseNullable(player);
if (u != null) { if (u != null) {
user = plugin.getUserManager().getIfLoaded(u); user = plugin.getUserManager().getIfLoaded(u);
@ -84,38 +79,20 @@ public class TreeCommand extends SingleCommand {
Message.USER_NOT_ONLINE.send(sender, player); Message.USER_NOT_ONLINE.send(sender, player);
return CommandResult.STATE_ERROR; return CommandResult.STATE_ERROR;
} }
} else {
user = null;
}
PermissionCache permissionData = user.getCachedData().getPermissionData(plugin.getContextForUser(user)); TreeView view = new TreeView(plugin.getPermissionVault(), selection);
TreeView view = TreeViewBuilder.newBuilder().rootPosition(selection).maxLevels(maxLevel).build(plugin.getPermissionVault());
if (!view.hasData()) { if (!view.hasData()) {
Message.TREE_EMPTY.send(sender); Message.TREE_EMPTY.send(sender);
return CommandResult.FAILURE; return CommandResult.FAILURE;
} }
Message.TREE_UPLOAD_START.send(sender); Message.TREE_UPLOAD_START.send(sender);
String url = view.uploadPasteData(plugin.getVersion(), user.getFriendlyName(), permissionData); PermissionCache permissionData = user == null ? null : user.getCachedData().getPermissionData(plugin.getContextForUser(user).orElse(plugin.getContextManager().getStaticContexts()));
String id = view.uploadPasteData(sender, user, permissionData);
Message.TREE_URL.send(sender); String url = plugin.getConfiguration().get(ConfigKeys.TREE_VIEWER_URL_PATTERN) + "?" + id;
Component message = TextComponent.builder(url).color(TextColor.AQUA)
.clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, String.valueOf(url)))
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to open the tree view.").color(TextColor.GRAY)))
.build();
sender.sendMessage(message);
return CommandResult.SUCCESS;
}
TreeView view = TreeViewBuilder.newBuilder().rootPosition(selection).maxLevels(maxLevel).build(plugin.getPermissionVault());
if (!view.hasData()) {
Message.TREE_EMPTY.send(sender);
return CommandResult.FAILURE;
}
Message.TREE_UPLOAD_START.send(sender);
String url = view.uploadPasteData(plugin.getVersion());
Message.TREE_URL.send(sender); Message.TREE_URL.send(sender);

View File

@ -31,6 +31,7 @@ import me.lucko.luckperms.common.commands.CommandPermission;
import me.lucko.luckperms.common.commands.CommandResult; import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.abstraction.SingleCommand; import me.lucko.luckperms.common.commands.abstraction.SingleCommand;
import me.lucko.luckperms.common.commands.sender.Sender; import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.locale.CommandSpec; import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message; import me.lucko.luckperms.common.locale.Message;
@ -64,8 +65,6 @@ public class VerboseCommand extends SingleCommand {
return CommandResult.INVALID_ARGS; return CommandResult.INVALID_ARGS;
} }
boolean noTraces = args.remove("--notrace") || args.remove("--notraces") || args.remove("--slim") || args.remove("-s");
boolean attachRaw = args.remove("--raw");
String mode = args.get(0).toLowerCase(); String mode = args.get(0).toLowerCase();
if (mode.equals("on") || mode.equals("true") || mode.equals("record")) { if (mode.equals("on") || mode.equals("true") || mode.equals("record")) {
@ -114,7 +113,8 @@ public class VerboseCommand extends SingleCommand {
Message.VERBOSE_OFF.send(sender); Message.VERBOSE_OFF.send(sender);
} else { } else {
Message.VERBOSE_UPLOAD_START.send(sender); Message.VERBOSE_UPLOAD_START.send(sender);
String url = listener.uploadPasteData(!noTraces, attachRaw); String id = listener.uploadPasteData();
String url = plugin.getConfiguration().get(ConfigKeys.VERBOSE_VIEWER_URL_PATTERN) + "?" + id;
Message.VERBOSE_RESULTS_URL.send(sender); Message.VERBOSE_RESULTS_URL.send(sender);

View File

@ -100,7 +100,7 @@ public class UserInfo extends SubCommand<User> {
String prefix = "&bNone"; String prefix = "&bNone";
String suffix = "&bNone"; String suffix = "&bNone";
String meta = "&bNone"; String meta = "&bNone";
Contexts contexts = plugin.getContextForUser(user); Contexts contexts = plugin.getContextForUser(user).orElse(null);
if (contexts != null) { if (contexts != null) {
ContextSet contextSet = contexts.getContexts(); ContextSet contextSet = contexts.getContexts();
if (!contextSet.isEmpty()) { if (!contextSet.isEmpty()) {

View File

@ -37,7 +37,7 @@ import me.lucko.luckperms.common.config.keys.IntegerKey;
import me.lucko.luckperms.common.config.keys.LowercaseStringKey; import me.lucko.luckperms.common.config.keys.LowercaseStringKey;
import me.lucko.luckperms.common.config.keys.MapKey; import me.lucko.luckperms.common.config.keys.MapKey;
import me.lucko.luckperms.common.config.keys.StringKey; import me.lucko.luckperms.common.config.keys.StringKey;
import me.lucko.luckperms.common.inheritance.graph.TraversalAlgorithm; import me.lucko.luckperms.common.graph.TraversalAlgorithm;
import me.lucko.luckperms.common.metastacking.SimpleMetaStackDefinition; import me.lucko.luckperms.common.metastacking.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.metastacking.StandardStackElements; import me.lucko.luckperms.common.metastacking.StandardStackElements;
import me.lucko.luckperms.common.model.TemporaryModifier; import me.lucko.luckperms.common.model.TemporaryModifier;
@ -476,7 +476,17 @@ public class ConfigKeys {
/** /**
* The URL of the web editor * The URL of the web editor
*/ */
public static final ConfigKey<String> WEB_EDITOR_URL_PATTERN = StringKey.of("web-editor-url", "https://lpedit.lucko.me/"); public static final ConfigKey<String> WEB_EDITOR_URL_PATTERN = StringKey.of("web-editor-url", "https://luckperms.github.io/editor/");
/**
* The URL of the verbose viewer
*/
public static final ConfigKey<String> VERBOSE_VIEWER_URL_PATTERN = StringKey.of("verbose-viewer-url", "https://luckperms.github.io/verbose/");
/**
* The URL of the tree viewer
*/
public static final ConfigKey<String> TREE_VIEWER_URL_PATTERN = StringKey.of("tree-viewer-url", "https://luckperms.github.io/treeview/");
private static Map<String, ConfigKey<?>> KEYS = null; private static Map<String, ConfigKey<?>> KEYS = null;

View File

@ -23,7 +23,7 @@
* SOFTWARE. * SOFTWARE.
*/ */
package me.lucko.luckperms.common.inheritance.graph; package me.lucko.luckperms.common.graph;
/** /**
* A minimal functional interface for graph-structured data. * A minimal functional interface for graph-structured data.

View File

@ -39,7 +39,7 @@
* limitations under the License. * limitations under the License.
*/ */
package me.lucko.luckperms.common.inheritance.graph; package me.lucko.luckperms.common.graph;
import com.google.common.collect.AbstractIterator; import com.google.common.collect.AbstractIterator;

View File

@ -23,7 +23,7 @@
* SOFTWARE. * SOFTWARE.
*/ */
package me.lucko.luckperms.common.inheritance.graph; package me.lucko.luckperms.common.graph;
public enum TraversalAlgorithm { public enum TraversalAlgorithm {

View File

@ -27,9 +27,9 @@ package me.lucko.luckperms.common.inheritance;
import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.Node; import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.common.inheritance.graph.Graph; import me.lucko.luckperms.common.graph.Graph;
import me.lucko.luckperms.common.inheritance.graph.GraphTraversers; import me.lucko.luckperms.common.graph.GraphTraversers;
import me.lucko.luckperms.common.inheritance.graph.TraversalAlgorithm; import me.lucko.luckperms.common.graph.TraversalAlgorithm;
import me.lucko.luckperms.common.model.Group; import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.PermissionHolder; import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.plugin.LuckPermsPlugin;

View File

@ -58,14 +58,12 @@ public enum CommandSpec {
VERBOSE("Manage verbose permission checking", "/%s verbose <true|false> [filter]", VERBOSE("Manage verbose permission checking", "/%s verbose <true|false> [filter]",
Arg.list( Arg.list(
Arg.create("on|record|off|paste", true, "whether to enable/disable logging, or to paste the logged output"), Arg.create("on|record|off|paste", true, "whether to enable/disable logging, or to paste the logged output"),
Arg.create("filter", false, "the filter to match entries against"), Arg.create("filter", false, "the filter to match entries against")
Arg.create("--slim", false, "add \"--slim\" to exclude trace data from the pasted output")
) )
), ),
TREE("Generate a tree view of permissions", "/%s tree [selection] [max level] [player]", TREE("Generate a tree view of permissions", "/%s tree [scope] [player]",
Arg.list( Arg.list(
Arg.create("selection", false, "the root of the tree. specify \".\" to include all permissions"), Arg.create("scope", false, "the root of the tree. specify \".\" to include all permissions"),
Arg.create("max level", false, "how many branch levels should be returned"),
Arg.create("player", false, "the name of an online player to check against") Arg.create("player", false, "the name of an online player to check against")
) )
), ),

View File

@ -63,8 +63,6 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.annotation.Nullable;
/** /**
* Main internal interface for LuckPerms plugins, providing the base for * Main internal interface for LuckPerms plugins, providing the base for
* abstraction throughout the project. * abstraction throughout the project.
@ -332,8 +330,7 @@ public interface LuckPermsPlugin {
* @param user the user instance * @param user the user instance
* @return a contexts object, or null if one couldn't be generated * @return a contexts object, or null if one couldn't be generated
*/ */
@Nullable Optional<Contexts> getContextForUser(User user);
Contexts getContextForUser(User user);
/** /**
* Gets the number of users online on the platform * Gets the number of users online on the platform

View File

@ -43,7 +43,7 @@ public class AllParentsByWeightHolder extends CachedPrimaryGroupHolder {
@Override @Override
protected String calculateValue() { protected String calculateValue() {
Contexts contexts = this.user.getPlugin().getContextForUser(this.user); Contexts contexts = this.user.getPlugin().getContextForUser(this.user).orElse(null);
if (contexts == null) { if (contexts == null) {
contexts = this.user.getPlugin().getContextManager().getStaticContexts(); contexts = this.user.getPlugin().getContextManager().getStaticContexts();
} }

View File

@ -40,7 +40,7 @@ public class ParentsByWeightHolder extends CachedPrimaryGroupHolder {
@Override @Override
protected String calculateValue() { protected String calculateValue() {
Contexts contexts = this.user.getPlugin().getContextForUser(this.user); Contexts contexts = this.user.getPlugin().getContextForUser(this.user).orElse(null);
if (contexts == null) { if (contexts == null) {
contexts = this.user.getPlugin().getContextManager().getStaticContexts(); contexts = this.user.getPlugin().getContextManager().getStaticContexts();
} }

View File

@ -27,6 +27,7 @@ package me.lucko.luckperms.common.treeview;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.gson.JsonObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -101,6 +102,19 @@ public class ImmutableTreeNode implements Comparable<ImmutableTreeNode> {
return results; return results;
} }
public JsonObject toJson(String prefix) {
if (this.children == null) {
return new JsonObject();
}
JsonObject object = new JsonObject();
for (Map.Entry<String, ImmutableTreeNode> entry : this.children.entrySet()) {
String name = prefix + entry.getKey();
object.add(name, entry.getValue().toJson(name + "."));
}
return object;
}
@Override @Override
public int compareTo(@Nonnull ImmutableTreeNode o) { public int compareTo(@Nonnull ImmutableTreeNode o) {
return (this.children != null) == o.getChildren().isPresent() ? 0 : (this.children != null ? 1 : -1); return (this.children != null) == o.getChildren().isPresent() ? 0 : (this.children != null ? 1 : -1);

View File

@ -26,22 +26,19 @@
package me.lucko.luckperms.common.treeview; package me.lucko.luckperms.common.treeview;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import com.google.common.base.Strings; import com.google.gson.JsonObject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.common.caching.type.PermissionCache; import me.lucko.luckperms.common.caching.type.PermissionCache;
import me.lucko.luckperms.common.utils.Gist; import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.verbose.CheckOrigin; import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.utils.gson.JObject;
import me.lucko.luckperms.common.utils.web.StandardPastebin;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
/** /**
* A readable view of a branch of {@link TreeNode}s. * A readable view of a branch of {@link TreeNode}s.
@ -52,15 +49,16 @@ public class TreeView {
// the root of the tree // the root of the tree
private final String rootPosition; private final String rootPosition;
// how many levels / branches to display
private final int maxLevel;
// the actual tree object // the actual tree object
private final ImmutableTreeNode view; private final ImmutableTreeNode view;
public TreeView(PermissionVault source, String rootPosition, int maxLevel) { public TreeView(PermissionVault source, String rootPosition) {
if (rootPosition.equals("") || rootPosition.equals("*")) {
rootPosition = ".";
} else if (!rootPosition.equals(".") && rootPosition.endsWith(".")) {
rootPosition = rootPosition.substring(0, rootPosition.length() - 1);
}
this.rootPosition = rootPosition; this.rootPosition = rootPosition;
this.maxLevel = maxLevel;
Optional<TreeNode> root = findRoot(rootPosition, source); Optional<TreeNode> root = findRoot(rootPosition, source);
this.view = root.map(TreeNode::makeImmutableCopy).orElse(null); this.view = root.map(TreeNode::makeImmutableCopy).orElse(null);
@ -115,152 +113,61 @@ public class TreeView {
} }
/** /**
* Converts the view to a readable list * Uploads the data contained in this TreeView and returns the id.
* *
* <p>The list contains KV pairs, where the key is the tree padding/structure, * @param sender the sender
* and the value is the actual permission.</p> * @param user the reference user, or null
* * @param checker the permission data instance to check against, or null
* @return a list of the nodes in this view * @return the id, or null
*/ */
private List<Map.Entry<String, String>> asTreeList() { public String uploadPasteData(Sender sender, User user, PermissionCache checker) {
// only paste if there is actually data here
if (!hasData()) {
throw new IllegalStateException();
}
// work out the prefix to apply // work out the prefix to apply
// since the view is relative, we need to prepend this to all permissions // since the view is relative, we need to prepend this to all permissions
String prefix = this.rootPosition.equals(".") ? "" : (this.rootPosition + "."); String prefix = this.rootPosition.equals(".") ? "" : (this.rootPosition + ".");
JsonObject jsonTree = this.view.toJson(prefix);
JObject metadata = new JObject()
.add("time", DATE_FORMAT.format(new Date(System.currentTimeMillis())))
.add("root", this.rootPosition)
.add("uploader", new JObject()
.add("name", sender.getNameWithLocation())
.add("uuid", sender.getUuid().toString())
);
List<Map.Entry<String, String>> ret = new ArrayList<>(); JObject checks;
if (user != null && checker != null) {
metadata.add("referenceUser", new JObject()
.add("name", user.getFriendlyName())
.add("uuid", user.getUuid().toString())
);
// iterate the node endings in the view checks = new JObject();
for (Map.Entry<Integer, String> s : this.view.getNodeEndings()) { for (Map.Entry<Integer, String> node : this.view.getNodeEndings()) {
// don't include the node if it exceeds the max level String permission = prefix + node.getValue();
if (s.getKey() >= this.maxLevel) { checks.add(permission, checker.getPermissionValue(permission).name().toLowerCase());
continue; }
} else {
checks = null;
} }
// generate the tree padding characters from the node level JsonObject payload = new JObject()
String treeStructure = Strings.repeat("", s.getKey()) + "├── "; .add("metadata", metadata)
// generate the permission, using the prefix and the node .add("data", new JObject()
String permission = prefix + s.getValue(); .add("tree", jsonTree)
.consume(obj -> {
ret.add(Maps.immutableEntry(treeStructure, permission)); if (checks != null) {
obj.add("checkResults", checks);
} }
})
)
.toJson();
return ret; return StandardPastebin.BYTEBIN.postJson(payload).id();
}
/**
* Uploads the data contained in this TreeView to a paste, and returns the URL.
*
* @param version the plugin version string
* @return the url, or null
*/
public String uploadPasteData(String version) {
// only paste if there is actually data here
if (!hasData()) {
throw new IllegalStateException();
}
// get the data contained in the view in a list form
// for each entry, the key is the padding tree characters
// and the value is the actual permission string
List<Map.Entry<String, String>> ret = asTreeList();
// build the header of the paste
ImmutableList.Builder<String> builder = getPasteHeader(version, "none", ret.size());
// add the tree data
builder.add("```");
for (Map.Entry<String, String> e : ret) {
builder.add(e.getKey() + e.getValue());
}
builder.add("```");
// clear the initial data map
ret.clear();
// upload the return the data
Gist gist = Gist.builder()
.description("LuckPerms Permission Tree")
.file("luckperms-tree.md", builder.build().stream().collect(Collectors.joining("\n")))
.upload();
return gist.getUrl();
}
/**
* Uploads the data contained in this TreeView to a paste, and returns the URL.
*
* <p>Unlike {@link #uploadPasteData(String)}, this method will check each permission
* against a corresponding user, and colorize the output depending on the check results.</p>
*
* @param version the plugin version string
* @param username the username of the reference user
* @param checker the permission data instance to check against
* @return the url, or null
*/
public String uploadPasteData(String version, String username, PermissionCache checker) {
// only paste if there is actually data here
if (!hasData()) {
throw new IllegalStateException();
}
// get the data contained in the view in a list form
// for each entry, the key is the padding tree characters
// and the value is the actual permission string
List<Map.Entry<String, String>> ret = asTreeList();
// build the header of the paste
ImmutableList.Builder<String> builder = getPasteHeader(version, username, ret.size());
// add the tree data
builder.add("```diff");
for (Map.Entry<String, String> e : ret) {
// lookup a permission value for the node
Tristate tristate = checker.getPermissionValue(e.getValue(), CheckOrigin.INTERNAL);
// append the data to the paste
builder.add(getTristateDiffPrefix(tristate) + e.getKey() + e.getValue());
}
builder.add("```");
// clear the initial data map
ret.clear();
// upload the return the data
Gist gist = Gist.builder()
.description("LuckPerms Permission Tree")
.file("luckperms-tree.md", builder.build().stream().collect(Collectors.joining("\n")))
.upload();
return gist.getUrl();
}
private static String getTristateDiffPrefix(Tristate t) {
switch (t) {
case TRUE:
return "+ ";
case FALSE:
return "- ";
default:
return "# ";
}
}
private ImmutableList.Builder<String> getPasteHeader(String version, String referenceUser, int size) {
String date = DATE_FORMAT.format(new Date(System.currentTimeMillis()));
String selection = this.rootPosition.equals(".") ? "any" : "`" + this.rootPosition + "`";
return ImmutableList.<String>builder()
.add("## Permission Tree")
.add("#### This file was automatically generated by [LuckPerms](https://github.com/lucko/LuckPerms) v" + version)
.add("")
.add("### Metadata")
.add("| Selection | Max Recursion | Reference User | Size | Produced at |")
.add("|-----------|---------------|----------------|------|-------------|")
.add("| " + selection + " | " + this.maxLevel + " | " + referenceUser + " | **" + size + "** | " + date + " |")
.add("")
.add("### Output");
} }
} }

View File

@ -1,189 +0,0 @@
/*
* 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.utils;
import com.google.common.collect.ImmutableList;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.stream.JsonWriter;
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a posted GitHub Gist
*/
public class Gist {
private static final Gson GSON = new Gson();
private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");
private static final String GIST_API_URL = "https://api.github.com/gists";
private static final String GIT_IO_URL = "https://git.io";
public static Builder builder() {
return new Builder();
}
private final String url;
private final String id;
private Gist(String url, String id) {
this.url = url;
this.id = id;
}
public String getUrl() {
return this.url;
}
public String getId() {
return this.id;
}
private static final class GistFile {
private final String name;
private final String content;
private GistFile(String name, String content) {
this.name = name;
this.content = content;
}
}
public static final class Builder {
private final List<GistFile> files = new ArrayList<>();
private boolean shorten = true;
private String description = "LuckPerms Gist";
private Builder() {
}
public Builder file(String name, String content) {
this.files.add(new GistFile(name, content));
return this;
}
public Builder shorten(boolean shorten) {
this.shorten = shorten;
return this;
}
public Builder description(String description) {
this.description = description;
return this;
}
public Gist upload() {
return Gist.upload(ImmutableList.copyOf(this.files), this.shorten, this.description);
}
}
private static Gist upload(List<GistFile> files, boolean shorten, String description) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (JsonWriter jw = new JsonWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) {
jw.beginObject();
jw.name("description").value(description);
jw.name("public").value(false);
jw.name("files").beginObject();
for (GistFile file : files) {
jw.name(file.name).beginObject().name("content").value(file.content).endObject();
}
jw.endObject().endObject();
} catch (IOException e) {
throw new RuntimeException(e);
}
RequestBody body = RequestBody.create(JSON_TYPE, out.toByteArray());
Request request = new Request.Builder()
.url(GIST_API_URL)
.post(body)
.build();
try (Response response = HttpClient.makeCall(request)) {
try (ResponseBody responseBody = response.body()) {
if (responseBody == null) {
throw new RuntimeException("No response");
}
String id;
String pasteUrl;
try (InputStream inputStream = responseBody.byteStream()) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
JsonObject object = GSON.fromJson(reader, JsonObject.class);
id = object.get("id").getAsString();
pasteUrl = object.get("html_url").getAsString();
}
}
if (shorten) {
pasteUrl = shortenUrl(pasteUrl);
}
return new Gist(pasteUrl, id);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static String shortenUrl(String pasteUrl) {
RequestBody requestBody = new FormBody.Builder()
.add("url", pasteUrl)
.build();
Request request = new Request.Builder()
.url(GIT_IO_URL)
.post(requestBody)
.build();
try (Response response = HttpClient.makeCall(request)) {
String location = response.header("Location");
if (location == null) {
throw new RuntimeException("No location header");
}
return location;
} catch (Exception e) {
e.printStackTrace();
}
return pasteUrl;
}
}

View File

@ -0,0 +1,110 @@
/*
* 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.utils;
import java.util.function.Consumer;
import java.util.function.Predicate;
public final class StackTracePrinter {
public static Consumer<StackTraceElement> elementToString(Consumer<String> consumer) {
return e -> consumer.accept(e.getClassName() + "." + e.getMethodName() + (e.getLineNumber() >= 0 ? ":" + e.getLineNumber() : ""));
}
public static Builder builder() {
return new Builder();
}
private final int truncateLength;
private final Predicate<StackTraceElement> shouldPrintPredicate;
private StackTracePrinter(int truncateLength, Predicate<StackTraceElement> shouldPrintPredicate) {
this.truncateLength = truncateLength;
this.shouldPrintPredicate = shouldPrintPredicate;
}
public int process(StackTraceElement[] stackTrace, Consumer<StackTraceElement> consumer) {
// how many lines have been printed
int count = 0;
// if we're printing elements yet
boolean printing = false;
for (StackTraceElement e : stackTrace) {
// start printing when the predicate passes
if (!printing && this.shouldPrintPredicate.test(e)) {
printing = true;
}
if (!printing) continue;
if (count >= this.truncateLength) break;
consumer.accept(e);
count++;
}
if (stackTrace.length > this.truncateLength) {
return stackTrace.length - this.truncateLength;
}
return 0;
}
public Builder toBuilder() {
Builder builder = new Builder();
builder.truncateLength = this.truncateLength;
builder.shouldPrintPredicate = this.shouldPrintPredicate;
return builder;
}
public static final class Builder {
private int truncateLength = Integer.MAX_VALUE;
private Predicate<StackTraceElement> shouldPrintPredicate = Predicates.alwaysTrue();
private Builder() {
}
public Builder truncateLength(int truncateLength) {
this.truncateLength = truncateLength;
return this;
}
public Builder ignoreElementsMatching(Predicate<? super StackTraceElement> predicate) {
this.shouldPrintPredicate = this.shouldPrintPredicate.and(predicate.negate());
return this;
}
public Builder ignoreClass(String className) {
return ignoreElementsMatching(e -> e.getClassName().equals(className));
}
public Builder ignoreClassStartingWith(String className) {
return ignoreElementsMatching(e -> e.getClassName().startsWith(className));
}
public StackTracePrinter build() {
return new StackTracePrinter(this.truncateLength, this.shouldPrintPredicate);
}
}
}

View File

@ -23,45 +23,42 @@
* SOFTWARE. * SOFTWARE.
*/ */
package me.lucko.luckperms.common.treeview; package me.lucko.luckperms.common.utils.gson;
/** import com.google.gson.JsonArray;
* Builds a {@link TreeView}. import com.google.gson.JsonElement;
*/
public class TreeViewBuilder { import java.util.function.Consumer;
public static TreeViewBuilder newBuilder() { import java.util.function.Supplier;
return new TreeViewBuilder();
public class JArray implements JElement {
private final JsonArray array = new JsonArray();
@Override
public JsonArray toJson() {
return this.array;
} }
private String rootPosition; public JArray add(String value) {
private int maxLevels; this.array.add(value);
private TreeViewBuilder() {
this.rootPosition = ".";
this.maxLevels = 5;
}
public TreeViewBuilder rootPosition(String rootPosition) {
this.rootPosition = rootPosition;
return this; return this;
} }
public TreeViewBuilder maxLevels(int maxLevels) { public JArray add(JsonElement value) {
this.maxLevels = maxLevels; this.array.add(value);
return this; return this;
} }
public TreeView build(PermissionVault source) { public JArray add(JElement value) {
if (this.maxLevels < 1) { return add(value.toJson());
this.maxLevels = 1;
}
if (this.rootPosition.equals("") || this.rootPosition.equals("*")) {
this.rootPosition = ".";
} else if (!this.rootPosition.equals(".") && this.rootPosition.endsWith(".")) {
this.rootPosition = this.rootPosition.substring(0, this.rootPosition.length() - 1);
} }
return new TreeView(source, this.rootPosition, this.maxLevels); public JArray add(Supplier<? extends JElement> value) {
return add(value.get().toJson());
} }
public JArray consume(Consumer<? super JArray> consumer) {
consumer.accept(this);
return this;
}
} }

View File

@ -0,0 +1,37 @@
/*
* 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.utils.gson;
import com.google.gson.JsonElement;
/**
* Stupidly simple fluent gson wrappers
*/
public interface JElement {
JsonElement toJson();
}

View File

@ -0,0 +1,74 @@
/*
* 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.utils.gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class JObject implements JElement {
private final JsonObject object = new JsonObject();
@Override
public JsonObject toJson() {
return this.object;
}
public JObject add(String key, String value) {
this.object.addProperty(key, value);
return this;
}
public JObject add(String key, Number value) {
this.object.addProperty(key, value);
return this;
}
public JObject add(String key, Boolean value) {
this.object.addProperty(key, value);
return this;
}
public JObject add(String key, JsonElement value) {
this.object.add(key, value);
return this;
}
public JObject add(String key, JElement value) {
return add(key, value.toJson());
}
public JObject add(String key, Supplier<? extends JElement> value) {
return add(key, value.get().toJson());
}
public JObject consume(Consumer<? super JObject> consumer) {
consumer.accept(this);
return this;
}
}

View File

@ -23,7 +23,7 @@
* SOFTWARE. * SOFTWARE.
*/ */
package me.lucko.luckperms.common.utils; package me.lucko.luckperms.common.utils.web;
import okhttp3.Interceptor; import okhttp3.Interceptor;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;

View File

@ -0,0 +1,43 @@
/*
* 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.utils.web;
import com.google.gson.JsonElement;
public interface Pastebin {
Paste postJson(JsonElement element);
Paste postPlain(String content);
String getRawUrl(String id);
interface Paste {
String url();
String id();
}
}

View File

@ -0,0 +1,161 @@
/*
* 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.utils.web;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
public enum StandardPastebin implements Pastebin {
BYTEBIN {
public static final String URL = "https://bytebin.lucko.me/";
private static final String POST_URL = URL + "post";
@Override
public String getPostUrl() {
return POST_URL;
}
@Override
protected String parseIdFromResult(BufferedReader reader) {
JsonObject object = GSON.fromJson(reader, JsonObject.class);
return object.get("key").getAsString();
}
@Override
public String getRawUrl(String id) {
return URL + id;
}
},
HASTEBIN {
private static final String URL = "https://hastebin.com/";
private static final String RAW_URL = URL + "raw/";
private static final String POST_URL = URL + "documents";
@Override
public String getPostUrl() {
return POST_URL;
}
@Override
protected String parseIdFromResult(BufferedReader reader) {
JsonObject object = GSON.fromJson(reader, JsonObject.class);
return object.get("key").getAsString();
}
@Override
public String getRawUrl(String id) {
return RAW_URL + id;
}
};
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");
private static final MediaType PLAIN_TYPE = MediaType.parse("text/plain; charset=utf-8");
protected abstract String getPostUrl();
protected abstract String parseIdFromResult(BufferedReader reader);
@Override
public Pastebin.Paste postJson(JsonElement content) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(bytes))) {
GSON.toJson(content, writer);
} catch (IOException e) {
throw new RuntimeException(e);
}
return post(RequestBody.create(JSON_TYPE, bytes.toByteArray()));
}
@Override
public Pastebin.Paste postPlain(String content) {
return post(RequestBody.create(PLAIN_TYPE, content));
}
private Pastebin.Paste post(RequestBody body) {
Request request = new Request.Builder()
.url(getPostUrl())
.post(body)
.build();
try (Response response = HttpClient.makeCall(request)) {
try (ResponseBody responseBody = response.body()) {
if (responseBody == null) {
throw new RuntimeException("No response");
}
try (InputStream inputStream = responseBody.byteStream()) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
String id = parseIdFromResult(reader);
String url = getRawUrl(id);
return new Paste(url, id);
}
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static final class Paste implements Pastebin.Paste {
private final String url;
private final String id;
private Paste(String url, String id) {
this.url = url;
this.id = id;
}
@Override
public String url() {
return this.url;
}
@Override
public String id() {
return this.id;
}
}
}

View File

@ -25,8 +25,15 @@
package me.lucko.luckperms.common.verbose; package me.lucko.luckperms.common.verbose;
import com.google.gson.JsonObject;
import me.lucko.luckperms.api.Tristate; import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.api.context.ImmutableContextSet; import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.common.utils.StackTracePrinter;
import me.lucko.luckperms.common.utils.gson.JArray;
import me.lucko.luckperms.common.utils.gson.JObject;
import java.util.Map;
/** /**
* Holds the data from a permission check * Holds the data from a permission check
@ -95,4 +102,33 @@ public class CheckData {
public Tristate getResult() { public Tristate getResult() {
return this.result; return this.result;
} }
private JObject formBaseJson() {
return new JObject()
.add("who", new JObject()
.add("identifier", this.checkTarget)
)
.add("permission", this.permission)
.add("result", this.result.name().toLowerCase())
.add("origin", this.checkOrigin.name().toLowerCase())
.add("context", new JArray()
.consume(arr -> {
for (Map.Entry<String, String> contextPair : this.checkContext.toSet()) {
arr.add(new JObject().add("key", contextPair.getKey()).add("value", contextPair.getValue()));
}
})
);
}
public JsonObject toJson() {
return formBaseJson().toJson();
}
public JsonObject toJson(StackTracePrinter tracePrinter) {
return formBaseJson()
.add("trace", new JArray()
.consume(arr -> tracePrinter.process(this.checkTrace, StackTracePrinter.elementToString(arr::add)))
)
.toJson();
}
} }

View File

@ -40,7 +40,6 @@ import java.util.concurrent.Executor;
* Accepts {@link CheckData} and passes it onto registered {@link VerboseListener}s. * Accepts {@link CheckData} and passes it onto registered {@link VerboseListener}s.
*/ */
public class VerboseHandler implements Runnable { public class VerboseHandler implements Runnable {
private final String pluginVersion;
// the listeners currently registered // the listeners currently registered
private final Map<UUID, VerboseListener> listeners; private final Map<UUID, VerboseListener> listeners;
@ -54,8 +53,7 @@ public class VerboseHandler implements Runnable {
// if the handler should shutdown // if the handler should shutdown
private boolean shutdown = false; private boolean shutdown = false;
public VerboseHandler(Executor executor, String pluginVersion) { public VerboseHandler(Executor executor) {
this.pluginVersion = "v" + pluginVersion;
this.listeners = new ConcurrentHashMap<>(); this.listeners = new ConcurrentHashMap<>();
this.queue = new ConcurrentLinkedQueue<>(); this.queue = new ConcurrentLinkedQueue<>();
@ -95,7 +93,7 @@ public class VerboseHandler implements Runnable {
* @param notify if the sender should be notified in chat on each check * @param notify if the sender should be notified in chat on each check
*/ */
public void registerListener(Sender sender, VerboseFilter filter, boolean notify) { public void registerListener(Sender sender, VerboseFilter filter, boolean notify) {
this.listeners.put(sender.getUuid(), new VerboseListener(this.pluginVersion, sender, filter, notify)); this.listeners.put(sender.getUuid(), new VerboseListener(sender, filter, notify));
this.listening = true; this.listening = true;
} }

View File

@ -25,7 +25,7 @@
package me.lucko.luckperms.common.verbose; package me.lucko.luckperms.common.verbose;
import com.google.common.collect.ImmutableList; import com.google.gson.JsonObject;
import me.lucko.luckperms.api.Tristate; import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.common.commands.CommandManager; import me.lucko.luckperms.common.commands.CommandManager;
@ -33,8 +33,11 @@ import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.CommandUtils; import me.lucko.luckperms.common.commands.utils.CommandUtils;
import me.lucko.luckperms.common.locale.Message; import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.utils.DateUtil; import me.lucko.luckperms.common.utils.DateUtil;
import me.lucko.luckperms.common.utils.Gist; import me.lucko.luckperms.common.utils.StackTracePrinter;
import me.lucko.luckperms.common.utils.TextUtils; import me.lucko.luckperms.common.utils.TextUtils;
import me.lucko.luckperms.common.utils.gson.JArray;
import me.lucko.luckperms.common.utils.gson.JObject;
import me.lucko.luckperms.common.utils.web.StandardPastebin;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
import net.kyori.text.event.HoverEvent; import net.kyori.text.event.HoverEvent;
@ -45,7 +48,6 @@ import java.util.Date;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors;
/** /**
* Accepts and processes {@link CheckData}, passed from the {@link VerboseHandler}. * Accepts and processes {@link CheckData}, passed from the {@link VerboseHandler}.
@ -55,17 +57,36 @@ public class VerboseListener {
// how much data should we store before stopping. // how much data should we store before stopping.
private static final int DATA_TRUNCATION = 10000; private static final int DATA_TRUNCATION = 10000;
// how many traces should we add
private static final int TRACE_DATA_TRUNCATION = 250;
// how many lines should we include in each stack trace send as a chat message // how many lines should we include in each stack trace send as a chat message
private static final int STACK_TRUNCATION_CHAT = 15; private static final int STACK_TRUNCATION_CHAT = 15;
// how many lines should we include in each stack trace in the web output // how many lines should we include in each stack trace in the web output
private static final int STACK_TRUNCATION_WEB = 30; private static final int STACK_TRUNCATION_WEB = 30;
private static final StackTracePrinter FILTERING_PRINTER = StackTracePrinter.builder()
.ignoreClassStartingWith("me.lucko.luckperms.")
.ignoreClassStartingWith("com.github.benmanes.caffeine")
.ignoreClass("java.util.concurrent.CompletableFuture")
.ignoreClass("java.util.concurrent.ConcurrentHashMap")
.build();
private static final StackTracePrinter CHAT_FILTERED_PRINTER = FILTERING_PRINTER.toBuilder()
.truncateLength(STACK_TRUNCATION_CHAT)
.build();
private static final StackTracePrinter CHAT_UNFILTERED_PRINTER = StackTracePrinter.builder()
.truncateLength(STACK_TRUNCATION_CHAT)
.build();
private static final StackTracePrinter WEB_FILTERED_PRINTER = FILTERING_PRINTER.toBuilder()
.truncateLength(STACK_TRUNCATION_WEB)
.build();
private static final StackTracePrinter WEB_UNFILTERED_PRINTER = StackTracePrinter.builder()
.truncateLength(STACK_TRUNCATION_WEB)
.build();
// the time when the listener was first registered // the time when the listener was first registered
private final long startTime = System.currentTimeMillis(); private final long startTime = System.currentTimeMillis();
// the version of the plugin. (used when we paste data to gist)
private final String pluginVersion;
// the sender to notify each time the listener processes a check which passes the filter // the sender to notify each time the listener processes a check which passes the filter
private final Sender notifiedSender; private final Sender notifiedSender;
// the filter // the filter
@ -80,8 +101,7 @@ public class VerboseListener {
// the checks which passed the filter, up to a max size of #DATA_TRUNCATION // the checks which passed the filter, up to a max size of #DATA_TRUNCATION
private final List<CheckData> results = new ArrayList<>(DATA_TRUNCATION / 10); private final List<CheckData> results = new ArrayList<>(DATA_TRUNCATION / 10);
public VerboseListener(String pluginVersion, Sender notifiedSender, VerboseFilter filter, boolean notify) { public VerboseListener(Sender notifiedSender, VerboseFilter filter, boolean notify) {
this.pluginVersion = pluginVersion;
this.notifiedSender = notifiedSender; this.notifiedSender = notifiedSender;
this.filter = filter; this.filter = filter;
this.notify = notify; this.notify = notify;
@ -132,7 +152,13 @@ public class VerboseListener {
hover.add("&bContext: &r" + CommandUtils.contextSetToString(data.getCheckContext())); hover.add("&bContext: &r" + CommandUtils.contextSetToString(data.getCheckContext()));
hover.add("&bTrace: &r"); hover.add("&bTrace: &r");
int overflow = readStack(data, STACK_TRUNCATION_CHAT, e -> hover.add("&7" + e.getClassName() + "." + e.getMethodName() + (e.getLineNumber() >= 0 ? ":" + e.getLineNumber() : ""))); Consumer<StackTraceElement> printer = StackTracePrinter.elementToString(str -> hover.add("&7" + str));
int overflow;
if (data.getCheckOrigin() == CheckOrigin.API || data.getCheckOrigin() == CheckOrigin.INTERNAL) {
overflow = CHAT_UNFILTERED_PRINTER.process(data.getCheckTrace(), printer);
} else {
overflow = CHAT_FILTERED_PRINTER.process(data.getCheckTrace(), printer);
}
if (overflow != 0) { if (overflow != 0) {
hover.add("&f... and " + overflow + " more"); hover.add("&f... and " + overflow + " more");
} }
@ -146,11 +172,9 @@ public class VerboseListener {
/** /**
* Uploads the captured data in this listener to a paste and returns the url * Uploads the captured data in this listener to a paste and returns the url
* *
* @param showTraces if stack traces should be included in the output
* @param attachRaw if the rawdata should be attached to the gist
* @return the url * @return the url
*/ */
public String uploadPasteData(boolean showTraces, boolean attachRaw) { public String uploadPasteData() {
// retrieve variables // retrieve variables
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
@ -163,148 +187,42 @@ public class VerboseListener {
if (this.filter.isBlank()){ if (this.filter.isBlank()){
filter = "any"; filter = "any";
} else { } else {
filter = "`" + this.filter.toString() + "`"; filter = this.filter.toString();
} }
// start building the message output boolean truncated = this.matchedCounter.get() > this.results.size();
ImmutableList.Builder<String> prettyOutput = ImmutableList.<String>builder()
.add("## Verbose Checking Output")
.add("#### This file was automatically generated by [LuckPerms](https://github.com/lucko/LuckPerms) " + this.pluginVersion)
.add("")
.add("### Metadata")
.add("| Key | Value |")
.add("|-----|-------|")
.add("| Start Time | " + startDate + " |")
.add("| End Time | " + endDate + " |")
.add("| Duration | " + duration +" |")
.add("| Count | **" + this.matchedCounter.get() + "** / " + this.counter.get() + " |")
.add("| User | " + this.notifiedSender.getNameWithLocation() + " |")
.add("| Filter | " + filter + " |")
.add("| Include traces | " + showTraces + " |")
.add("");
// warn if data was truncated JObject metadata = new JObject()
if (this.matchedCounter.get() > this.results.size()) { .add("startTime", startDate)
prettyOutput.add("**WARN:** Result set exceeded max size of " + DATA_TRUNCATION + ". The output below was truncated to " + DATA_TRUNCATION + " entries."); .add("endTime", endDate)
prettyOutput.add(""); .add("duration", duration)
} .add("count", new JObject()
.add("matched", this.matchedCounter.get())
// explain why some traces may be missing .add("total", this.counter.get())
if (showTraces && this.results.size() > TRACE_DATA_TRUNCATION) { )
prettyOutput.add("**WARN:** Result set exceeded size of " + TRACE_DATA_TRUNCATION + ". The traced output below was truncated to " + TRACE_DATA_TRUNCATION + " entries. "); .add("uploader", new JObject()
prettyOutput.add("Either refine the query using a more specific filter, or disable tracing by adding '--slim' to the end of the paste command."); .add("name", this.notifiedSender.getNameWithLocation())
prettyOutput.add(""); .add("uuid", this.notifiedSender.getUuid().toString())
} )
.add("filter", filter)
// print the format of the output .add("truncated", truncated);
prettyOutput.add("### Output")
.add("Format: `<checked>` `<permission>` `<value>`")
.add("")
.add("___")
.add("");
// build the csv output - will only be appended to if this is enabled.
ImmutableList.Builder<String> csvOutput = ImmutableList.<String>builder()
.add("User,Permission,Result");
// how many instances have been printed so far
AtomicInteger printedCount = new AtomicInteger(0);
JArray data = new JArray();
for (CheckData c : this.results) { for (CheckData c : this.results) {
if (!showTraces) { if (c.getCheckOrigin() == CheckOrigin.API || c.getCheckOrigin() == CheckOrigin.INTERNAL) {
data.add(c.toJson(WEB_UNFILTERED_PRINTER));
// if traces aren't being shown, just append using raw markdown
prettyOutput.add("`" + c.getCheckTarget() + "` - " + c.getPermission() + " - " + getTristateSymbol(c.getResult()) + " ");
} else if (printedCount.incrementAndGet() > TRACE_DATA_TRUNCATION) {
// if we've gone over the trace truncation, just append the raw info.
// we still have to use html, as the rest of this section is still using it.
prettyOutput.add("<br><code>" + c.getCheckTarget() + "</code> - " + c.getPermission() + " - " + getTristateSymbol(c.getResult()));
} else { } else {
data.add(c.toJson(WEB_FILTERED_PRINTER));
// append the full output.
prettyOutput.add("<details><summary><code>" + c.getCheckTarget() + "</code> - " + c.getPermission() + " - " + getTristateSymbol(c.getResult()) + "</summary><p>");
// append the spoiler text
prettyOutput.add("<br><b>Origin:</b> <code>" + c.getCheckOrigin().name() + "</code>");
prettyOutput.add("<br><b>Context:</b> <code>" + CommandUtils.stripColor(CommandUtils.contextSetToString(c.getCheckContext())) + "</code>");
prettyOutput.add("<br><b>Trace:</b><pre>");
int overflow = readStack(c, STACK_TRUNCATION_WEB, e -> prettyOutput.add(e.getClassName() + "." + e.getMethodName() + (e.getLineNumber() >= 0 ? ":" + e.getLineNumber() : "")));
if (overflow != 0) {
prettyOutput.add("... and " + overflow + " more");
}
prettyOutput.add("</pre></p></details>");
}
// if we're including a raw csv output, append that too
if (attachRaw) {
csvOutput.add(escapeCommas(c.getCheckTarget()) + "," + escapeCommas(c.getPermission()) + "," + c.getResult().name().toLowerCase());
} }
} }
this.results.clear(); this.results.clear();
Gist.Builder gist = Gist.builder() JsonObject payload = new JObject()
.description("LuckPerms Verbose Checking Output") .add("metadata", metadata)
.file("luckperms-verbose.md", prettyOutput.build().stream().collect(Collectors.joining("\n"))); .add("data", data)
.toJson();
if (attachRaw) { return StandardPastebin.BYTEBIN.postJson(payload).id();
gist.file("raw-data.csv", csvOutput.build().stream().collect(Collectors.joining("\n")));
}
return gist.upload().getUrl();
}
/**
* Reads a stack trace from a {@link CheckData} instance.
*
* @param data the data to read from
* @param truncateLength the length when we should stop reading the stack
* @param consumer the element consumer
* @return how many elements were left unread, or 0 if everything was read
*/
private static int readStack(CheckData data, int truncateLength, Consumer<StackTraceElement> consumer) {
StackTraceElement[] stack = data.getCheckTrace();
// how many lines have been printed
int count = 0;
// if we're printing elements yet
boolean printing = false;
for (StackTraceElement e : stack) {
// start printing when we escape LP internals code
boolean shouldStartPrinting = !printing && (
(data.getCheckOrigin() == CheckOrigin.API || data.getCheckOrigin() == CheckOrigin.INTERNAL) || (
!e.getClassName().startsWith("me.lucko.luckperms.") &&
// all used within the checking impl somewhere
!e.getClassName().equals("java.util.concurrent.CompletableFuture") &&
!e.getClassName().startsWith("com.github.benmanes.caffeine") &&
!e.getClassName().equals("java.util.concurrent.ConcurrentHashMap")
)
);
if (shouldStartPrinting) {
printing = true;
}
if (!printing) continue;
if (count >= truncateLength) break;
consumer.accept(e);
count++;
}
if (stack.length > truncateLength) {
return stack.length - truncateLength;
}
return 0;
}
private static String escapeCommas(String s) {
return s.contains(",") ? "\"" + s + "\"" : s;
} }
private static String getTristateColor(Tristate tristate) { private static String getTristateColor(Tristate tristate) {
@ -318,17 +236,6 @@ public class VerboseListener {
} }
} }
private static String getTristateSymbol(Tristate tristate) {
switch (tristate) {
case TRUE:
return "✔️";
case FALSE:
return "";
default:
return "";
}
}
public Sender getNotifiedSender() { public Sender getNotifiedSender() {
return this.notifiedSender; return this.notifiedSender;
} }

View File

@ -30,7 +30,6 @@ import com.google.gson.Gson;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import me.lucko.luckperms.api.context.ImmutableContextSet; import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.common.commands.sender.Sender; import me.lucko.luckperms.common.commands.sender.Sender;
@ -41,9 +40,11 @@ import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.NodeModel; import me.lucko.luckperms.common.node.NodeModel;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Gist;
import me.lucko.luckperms.common.utils.HttpClient;
import me.lucko.luckperms.common.utils.Uuids; import me.lucko.luckperms.common.utils.Uuids;
import me.lucko.luckperms.common.utils.gson.JArray;
import me.lucko.luckperms.common.utils.gson.JObject;
import me.lucko.luckperms.common.utils.web.HttpClient;
import me.lucko.luckperms.common.utils.web.StandardPastebin;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
@ -66,54 +67,49 @@ import java.util.stream.Stream;
public final class WebEditor { public final class WebEditor {
private static final Gson GSON = new Gson(); private static final Gson GSON = new Gson();
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 USER_ID_PATTERN = "user/";
private static final String GROUP_ID_PATTERN = "group/"; private static final String GROUP_ID_PATTERN = "group/";
private static void writeData(PermissionHolder holder, JsonObject payload) { private static JObject writeData(PermissionHolder holder) {
payload.addProperty("who", getHolderIdentifier(holder)); return new JObject()
payload.addProperty("whoFriendly", holder.getFriendlyName()); .add("who", new JObject()
.add("id", getHolderIdentifier(holder))
.add("friendly", holder.getFriendlyName())
.consume(obj -> {
if (holder.getType().isUser()) { if (holder.getType().isUser()) {
payload.addProperty("whoUuid", ((User) holder).getUuid().toString()); obj.add("uuid", ((User) holder).getUuid().toString());
} }
}))
// attach the holders permissions .add("nodes", serializePermissions(holder.getEnduringNodes().values().stream().map(NodeModel::fromNode)));
payload.add("nodes", serializePermissions(holder.getEnduringNodes().values().stream().map(NodeModel::fromNode)));
} }
public static JsonObject formPayload(List<PermissionHolder> holders, Sender sender, String cmdLabel, LuckPermsPlugin plugin) { public static JsonObject formPayload(List<PermissionHolder> holders, Sender sender, String cmdLabel, LuckPermsPlugin plugin) {
Preconditions.checkArgument(!holders.isEmpty(), "holders is empty"); Preconditions.checkArgument(!holders.isEmpty(), "holders is empty");
// form the payload data // form the payload data
JsonObject payload = new JsonObject(); return new JObject()
.add("metadata", new JObject()
payload.addProperty("cmdAlias", cmdLabel); .add("cmdAlias", cmdLabel)
payload.addProperty("uploadedBy", sender.getNameWithLocation()); .add("uploader", new JObject()
payload.addProperty("uploadedByUuid", sender.getUuid().toString()); .add("name", sender.getNameWithLocation())
payload.addProperty("time", System.currentTimeMillis()); .add("uuid", sender.getUuid().toString())
)
if (holders.size() == 1) { .add("time", System.currentTimeMillis())
writeData(holders.get(0), payload); )
} else { .add("sessions", new JArray()
JsonArray tabs = new JsonArray(); .consume(arr -> {
for (PermissionHolder holder : holders) { for (PermissionHolder holder : holders) {
JsonObject o = new JsonObject(); arr.add(writeData(holder));
writeData(holder, o);
tabs.add(o);
} }
payload.add("tabs", tabs); })
} )
.add("knownPermissions", new JArray()
// attach an array of all permissions known to the server, to use for tab completion in the editor .consume(arr -> {
JsonArray knownPermsArray = new JsonArray();
for (String perm : plugin.getPermissionVault().rootAsList()) { for (String perm : plugin.getPermissionVault().rootAsList()) {
knownPermsArray.add(new JsonPrimitive(perm)); arr.add(perm);
} }
payload.add("knownPermissions", knownPermsArray); })
).toJson();
return payload;
} }
private static String getHolderIdentifier(PermissionHolder holder) { private static String getHolderIdentifier(PermissionHolder holder) {
@ -152,19 +148,9 @@ public final class WebEditor {
} }
} }
public static String postToGist(String content) {
Gist gist = Gist.builder()
.description("LuckPerms Web Editor Data")
.shorten(false)
.file(FILE_NAME, content)
.upload();
return gist.getId();
}
public static JsonObject getDataFromGist(String id) { public static JsonObject getDataFromGist(String id) {
Request request = new Request.Builder() Request request = new Request.Builder()
.url(GIST_API_URL + "/" + id) .url(StandardPastebin.BYTEBIN.getRawUrl(id))
.build(); .build();
try (Response response = HttpClient.makeCall(request)) { try (Response response = HttpClient.makeCall(request)) {
@ -175,29 +161,7 @@ public final class WebEditor {
try (InputStream inputStream = responseBody.byteStream()) { try (InputStream inputStream = responseBody.byteStream()) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
JsonObject object = new Gson().fromJson(reader, JsonObject.class); return GSON.fromJson(reader, JsonObject.class);
JsonObject files = object.get("files").getAsJsonObject();
JsonObject permsFile = files.get(FILE_NAME).getAsJsonObject();
// uh..
if (permsFile.get("truncated").getAsBoolean()) {
try (Response rawResponse = HttpClient.makeCall(new Request.Builder().url(permsFile.get("raw_url").getAsString()).build())) {
try (ResponseBody rawResponseBody = rawResponse.body()) {
if (rawResponseBody == null) {
throw new RuntimeException("No response");
}
try (InputStream rawInputStream = rawResponseBody.byteStream()) {
try (BufferedReader rawReader = new BufferedReader(new InputStreamReader(rawInputStream, StandardCharsets.UTF_8))) {
return GSON.fromJson(rawReader, JsonObject.class);
}
}
}
}
} else {
String content = permsFile.get("content").getAsString();
return GSON.fromJson(content, JsonObject.class);
}
} }
} }
} }

View File

@ -103,8 +103,6 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.annotation.Nullable;
/** /**
* LuckPerms implementation for the Nukkit API. * LuckPerms implementation for the Nukkit API.
*/ */
@ -157,7 +155,7 @@ public class LPNukkitPlugin extends PluginBase implements LuckPermsPlugin {
public void onEnable() { public void onEnable() {
this.startTime = System.currentTimeMillis(); this.startTime = System.currentTimeMillis();
sendStartupBanner(getConsoleSender()); sendStartupBanner(getConsoleSender());
this.verboseHandler = new VerboseHandler(this.scheduler.asyncNukkit(), getVersion()); this.verboseHandler = new VerboseHandler(this.scheduler.asyncNukkit());
this.permissionVault = new PermissionVault(this.scheduler.asyncNukkit()); this.permissionVault = new PermissionVault(this.scheduler.asyncNukkit());
this.logDispatcher = new LogDispatcher(this); this.logDispatcher = new LogDispatcher(this);
@ -469,14 +467,13 @@ public class LPNukkitPlugin extends PluginBase implements LuckPermsPlugin {
return Optional.empty(); return Optional.empty();
} }
@Nullable
@Override @Override
public Contexts getContextForUser(User user) { public Optional<Contexts> getContextForUser(User user) {
Player player = getPlayer(user); Player player = getPlayer(user);
if (player == null) { if (player == null) {
return null; return Optional.empty();
} }
return this.contextManager.getApplicableContexts(player); return Optional.of(this.contextManager.getApplicableContexts(player));
} }
@Override @Override

View File

@ -123,8 +123,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.annotation.Nullable;
/** /**
* LuckPerms implementation for the Sponge API. * LuckPerms implementation for the Sponge API.
*/ */
@ -203,7 +201,7 @@ public class LPSpongePlugin implements LuckPermsSpongePlugin {
this.dependencyManager.loadDependencies(DependencyRegistry.GLOBAL_DEPENDENCIES); this.dependencyManager.loadDependencies(DependencyRegistry.GLOBAL_DEPENDENCIES);
sendStartupBanner(getConsoleSender()); sendStartupBanner(getConsoleSender());
this.verboseHandler = new VerboseHandler(this.scheduler.async(), getVersion()); this.verboseHandler = new VerboseHandler(this.scheduler.async());
this.permissionVault = new PermissionVault(this.scheduler.async()); this.permissionVault = new PermissionVault(this.scheduler.async());
this.logDispatcher = new LogDispatcher(this); this.logDispatcher = new LogDispatcher(this);
@ -429,14 +427,13 @@ public class LPSpongePlugin implements LuckPermsSpongePlugin {
} }
} }
@Nullable
@Override @Override
public Contexts getContextForUser(User user) { public Optional<Contexts> getContextForUser(User user) {
Player player = getPlayer(user); Player player = getPlayer(user);
if (player == null) { if (player == null) {
return null; return Optional.empty();
} }
return this.contextManager.getApplicableContexts(player); return Optional.of(this.contextManager.getApplicableContexts(player));
} }
@Override @Override