mirror of
https://github.com/LuckPerms/LuckPerms.git
synced 2024-11-24 11:38:40 +01:00
refactor the way verbose filters are parsed - tokenize on first init as opposed to on each check
This commit is contained in:
parent
5ae90f2a4b
commit
05ac7e6041
@ -36,6 +36,7 @@ import me.lucko.luckperms.common.locale.LocaleManager;
|
||||
import me.lucko.luckperms.common.locale.Message;
|
||||
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||
import me.lucko.luckperms.common.utils.Predicates;
|
||||
import me.lucko.luckperms.common.verbose.InvalidFilterException;
|
||||
import me.lucko.luckperms.common.verbose.VerboseFilter;
|
||||
import me.lucko.luckperms.common.verbose.VerboseListener;
|
||||
|
||||
@ -75,14 +76,18 @@ public class VerboseCommand extends SingleCommand {
|
||||
|
||||
String filter = filters.isEmpty() ? "" : filters.stream().collect(Collectors.joining(" "));
|
||||
|
||||
if (!VerboseFilter.isValidFilter(filter)) {
|
||||
VerboseFilter parsedFilter;
|
||||
try {
|
||||
parsedFilter = VerboseFilter.parse(filter);
|
||||
} catch (InvalidFilterException e) {
|
||||
e.printStackTrace();
|
||||
Message.VERBOSE_INVALID_FILTER.send(sender, filter);
|
||||
return CommandResult.FAILURE;
|
||||
}
|
||||
|
||||
boolean notify = !mode.equals("record");
|
||||
|
||||
plugin.getVerboseHandler().registerListener(sender, filter, notify);
|
||||
plugin.getVerboseHandler().registerListener(sender, parsedFilter, notify);
|
||||
|
||||
if (notify) {
|
||||
if (!filter.equals("")) {
|
||||
|
@ -33,30 +33,21 @@ public enum CheckOrigin {
|
||||
/**
|
||||
* Indicates the check was caused by a 'hasPermission' check on the platform
|
||||
*/
|
||||
PLATFORM_PERMISSION_CHECK('C'),
|
||||
PLATFORM_PERMISSION_CHECK,
|
||||
|
||||
/**
|
||||
* Indicates the check was caused by a 'hasPermissionSet' type check on the platform
|
||||
*/
|
||||
PLATFORM_LOOKUP_CHECK('L'),
|
||||
PLATFORM_LOOKUP_CHECK,
|
||||
|
||||
/**
|
||||
* Indicates the check was caused by an API call
|
||||
*/
|
||||
API('A'),
|
||||
API,
|
||||
|
||||
/**
|
||||
* Indicates the check was caused by a LuckPerms internal
|
||||
*/
|
||||
INTERNAL('I');
|
||||
INTERNAL
|
||||
|
||||
private final char code;
|
||||
|
||||
CheckOrigin(char code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public char getCode() {
|
||||
return this.code;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.verbose;
|
||||
|
||||
/**
|
||||
* Exception thrown when attempting to compile a {@link VerboseFilter}
|
||||
* using an invalid filter string.
|
||||
*/
|
||||
public class InvalidFilterException extends Exception {
|
||||
|
||||
public InvalidFilterException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
@ -25,141 +25,266 @@
|
||||
|
||||
package me.lucko.luckperms.common.verbose;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import me.lucko.luckperms.common.utils.Scripting;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptException;
|
||||
|
||||
/**
|
||||
* Tests verbose filters
|
||||
* Represents a verbose filter expression.
|
||||
*
|
||||
* <p>The filter is parsed when the instance is initialised - subsequent
|
||||
* evaluations should be relatively fast.</p>
|
||||
*/
|
||||
public final class VerboseFilter {
|
||||
|
||||
// the characters used in an expression which are part of the expression
|
||||
// syntax - and not the filter itself.
|
||||
private static final String DELIMITERS = " |&()!";
|
||||
|
||||
// the script engine to use when evaluating the expression
|
||||
private final ScriptEngine engine;
|
||||
// the parsed expression
|
||||
private final List<Token> expression;
|
||||
|
||||
/**
|
||||
* Evaluates whether the passed check data passes the filter
|
||||
* Compiles a {@link VerboseFilter} instance for the given filter string
|
||||
*
|
||||
* @param filter the filter
|
||||
* @return a filter
|
||||
* @throws InvalidFilterException if the filter is invalid
|
||||
*/
|
||||
public static VerboseFilter parse(String filter) throws InvalidFilterException {
|
||||
ScriptEngine engine = Scripting.getScriptEngine();
|
||||
if (engine == null) {
|
||||
throw new RuntimeException("Script engine not present");
|
||||
}
|
||||
|
||||
return new VerboseFilter(engine, filter);
|
||||
}
|
||||
|
||||
private VerboseFilter(ScriptEngine engine, String filter) throws InvalidFilterException {
|
||||
this.engine = engine;
|
||||
|
||||
if (filter.isEmpty()) {
|
||||
this.expression = ImmutableList.of();
|
||||
} else {
|
||||
try {
|
||||
this.expression = generateExpression(engine, filter);
|
||||
} catch (Exception e) {
|
||||
throw new InvalidFilterException("Exception occurred whilst generating an expression for '" + filter + "'", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a filter string into a list of 'tokens' forming the expression.
|
||||
*
|
||||
* Each token either represents part of the expressions syntax
|
||||
* (logical and, logical or, brackets, or space) or a value.
|
||||
*
|
||||
* @param engine the script engine to test the expression with
|
||||
* @param filter the filter string
|
||||
* @return a parsed list of expressions
|
||||
* @throws ScriptException if the engine throws an exception whilst evaluating the expression
|
||||
*/
|
||||
private static List<Token> generateExpression(ScriptEngine engine, String filter) throws ScriptException {
|
||||
// tokenize the filter using the filter characters as delimiters.
|
||||
StringTokenizer tokenizer = new StringTokenizer(filter, DELIMITERS, true);
|
||||
|
||||
// use the tokenizer to parse the string to a list of 'tokens'.
|
||||
ImmutableList.Builder<Token> expressionBuilder = ImmutableList.builder();
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String token = tokenizer.nextToken();
|
||||
|
||||
if (isDelimiter(token)) {
|
||||
// if the token is a delimiter, just append it to the expression as a constant
|
||||
expressionBuilder.add(new ConstantToken(token));
|
||||
} else {
|
||||
// otherwise consider it to be a value
|
||||
expressionBuilder.add(new VariableToken(token));
|
||||
}
|
||||
}
|
||||
|
||||
// build & test the expression
|
||||
List<Token> expression = expressionBuilder.build();
|
||||
testExpression(expression, engine);
|
||||
return expression;
|
||||
}
|
||||
|
||||
private static void testExpression(List<Token> expression, ScriptEngine engine) throws ScriptException {
|
||||
// build a dummy version of the expression.
|
||||
// all values are simply replaced by "true"
|
||||
String dummyExpression = expression.stream().map(Token::forDummyExpression).collect(Collectors.joining());
|
||||
|
||||
// do a test run - if the engine returns a result without throwing an exception
|
||||
// and the result is a boolean, we can consider the expression to be valid
|
||||
String result = engine.eval(dummyExpression).toString();
|
||||
if (!result.equals("true") && !result.equals("false")) {
|
||||
throw new IllegalArgumentException("Expected true/false but got '" + result + "' instead.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates whether the check data passes the filter
|
||||
*
|
||||
* @param data the check data
|
||||
* @param filter the filter
|
||||
* @return if the check data passes the filter
|
||||
*/
|
||||
public static boolean passesFilter(CheckData data, String filter) {
|
||||
if (filter.equals("")) {
|
||||
public boolean evaluate(CheckData data) {
|
||||
if (this.expression.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// get the script engine
|
||||
ScriptEngine engine = Scripting.getScriptEngine();
|
||||
if (engine == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// tokenize the filter
|
||||
StringTokenizer tokenizer = new StringTokenizer(filter, " |&()!", true);
|
||||
|
||||
// build an expression which can be evaluated by the javascript engine
|
||||
StringBuilder expressionBuilder = new StringBuilder();
|
||||
|
||||
// read the tokens
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String token = tokenizer.nextToken();
|
||||
|
||||
// if the token is a delimiter, just append it to the expression
|
||||
if (isDelim(token)) {
|
||||
expressionBuilder.append(token);
|
||||
|
||||
} else {
|
||||
|
||||
// if the token is not a delimiter, it must be a string.
|
||||
// we replace non-delimiters with a boolean depending on if the string matches the check data.
|
||||
boolean value = data.getCheckTarget().equalsIgnoreCase(token) ||
|
||||
data.getPermission().toLowerCase().startsWith(token.toLowerCase()) ||
|
||||
data.getResult().name().equalsIgnoreCase(token);
|
||||
|
||||
expressionBuilder.append(value);
|
||||
}
|
||||
}
|
||||
|
||||
// build the expression
|
||||
String expression = expressionBuilder.toString().replace("&", "&&").replace("|", "||");
|
||||
// build an expression string for the passed check data.
|
||||
String expressionString = this.expression.stream().map(token -> token.forExpression(data)).collect(Collectors.joining());
|
||||
|
||||
// evaluate the expression using the script engine
|
||||
try {
|
||||
String result = engine.eval(expression).toString();
|
||||
String result = this.engine.eval(expressionString).toString();
|
||||
|
||||
// validate return value
|
||||
if (!result.equals("true") && !result.equals("false")) {
|
||||
throw new IllegalArgumentException(expression + " - " + result);
|
||||
throw new IllegalArgumentException("Expected true/false but got '" + result + "' instead.");
|
||||
}
|
||||
|
||||
// return the result of the expression
|
||||
return Boolean.parseBoolean(result);
|
||||
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
} catch (Throwable ex) {
|
||||
// print the error & return false
|
||||
ex.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
public boolean isBlank() {
|
||||
return this.expression.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.expression.stream().map(Token::toString).collect(Collectors.joining());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether a filter is valid
|
||||
* Returns true if the string is equal to one of the {@link #DELIMITERS}.
|
||||
*
|
||||
* @param filter the filter to test
|
||||
* @return true if the filter is valid
|
||||
* @param string the string
|
||||
* @return true if delimiter, false otherwise
|
||||
*/
|
||||
public static boolean isValidFilter(String filter) {
|
||||
if (filter.equals("")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// get the script engine
|
||||
ScriptEngine engine = Scripting.getScriptEngine();
|
||||
if (engine == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// tokenize the filter
|
||||
StringTokenizer tokenizer = new StringTokenizer(filter, " |&()!", true);
|
||||
|
||||
// build an expression which can be evaluated by the javascript engine
|
||||
StringBuilder expressionBuilder = new StringBuilder();
|
||||
|
||||
// read the tokens
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String token = tokenizer.nextToken();
|
||||
|
||||
// if the token is a delimiter, just append it to the expression
|
||||
if (isDelim(token)) {
|
||||
expressionBuilder.append(token);
|
||||
} else {
|
||||
expressionBuilder.append("true"); // dummy result
|
||||
}
|
||||
}
|
||||
|
||||
// build the expression
|
||||
String expression = expressionBuilder.toString().replace("&", "&&").replace("|", "||");
|
||||
|
||||
// evaluate the expression using the script engine
|
||||
try {
|
||||
String result = engine.eval(expression).toString();
|
||||
if (!result.equals("true") && !result.equals("false")) {
|
||||
throw new IllegalArgumentException(expression + " - " + result);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Throwable t) {
|
||||
return false;
|
||||
private static boolean isDelimiter(String string) {
|
||||
switch (string.charAt(0)) {
|
||||
case ' ':
|
||||
case '|':
|
||||
case '&':
|
||||
case '(':
|
||||
case ')':
|
||||
case '!':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDelim(String token) {
|
||||
return token.equals(" ") ||
|
||||
token.equals("|") ||
|
||||
token.equals("&") ||
|
||||
token.equals("(") ||
|
||||
token.equals(")") ||
|
||||
token.equals("!");
|
||||
/**
|
||||
* Represents a part of an expression
|
||||
*/
|
||||
private interface Token {
|
||||
|
||||
/**
|
||||
* Returns the value of this token when part of an evaluated expression
|
||||
*
|
||||
* @param data the data which an expression is being formed for
|
||||
* @return the value to be used as part of the evaluated expression
|
||||
*/
|
||||
String forExpression(CheckData data);
|
||||
|
||||
/**
|
||||
* Returns a 'dummy' value for this token in order to build a test
|
||||
* expression.
|
||||
*
|
||||
* @return the value to be used as part of the test expression
|
||||
*/
|
||||
String forDummyExpression();
|
||||
|
||||
}
|
||||
|
||||
private VerboseFilter() {}
|
||||
/**
|
||||
* Represents a constant part of the expression - tokens will only ever
|
||||
* consist of the characters defined in the {@link #DELIMITERS} string.
|
||||
*/
|
||||
private static final class ConstantToken implements Token {
|
||||
private final String string;
|
||||
|
||||
private ConstantToken(String string) {
|
||||
// replace single '&' and '|' character with double values
|
||||
if (string.equals("&")) {
|
||||
string = "&&";
|
||||
} else if (string.equals("|")) {
|
||||
string = "||";
|
||||
}
|
||||
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String forExpression(CheckData data) {
|
||||
return this.string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String forDummyExpression() {
|
||||
return this.string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a variable part of the token. When evaluated as an expression,
|
||||
* this token will be replaced with a boolean 'true' or 'false' - depending
|
||||
* on if the passed check data "matches" the value of this token.
|
||||
*
|
||||
* The check data will be deemed a "match" if:
|
||||
* - the target of the check is equal to the value of the token
|
||||
* - the permission being checked for starts with the value of the token
|
||||
* - the result of the check is equal to the value of the token
|
||||
*/
|
||||
private static final class VariableToken implements Token {
|
||||
private final String value;
|
||||
|
||||
private VariableToken(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String forExpression(CheckData data) {
|
||||
return Boolean.toString(
|
||||
data.getCheckTarget().equalsIgnoreCase(this.value) ||
|
||||
data.getPermission().toLowerCase().startsWith(this.value.toLowerCase()) ||
|
||||
data.getResult().name().equalsIgnoreCase(this.value)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String forDummyExpression() {
|
||||
return "true";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ public class VerboseHandler implements Runnable {
|
||||
* @param filter the filter string
|
||||
* @param notify if the sender should be notified in chat on each check
|
||||
*/
|
||||
public void registerListener(Sender sender, String 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.listening = true;
|
||||
}
|
||||
|
@ -57,36 +57,32 @@ public class VerboseListener {
|
||||
|
||||
// how much data should we store before stopping.
|
||||
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
|
||||
private static final int STACK_TRUNCATION_CHAT = 15;
|
||||
// how many lines should we include in each stack trace in the web output
|
||||
private static final int STACK_TRUNCATION_WEB = 30;
|
||||
|
||||
// the time when the listener was first registered
|
||||
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
|
||||
private final Sender notifiedSender;
|
||||
|
||||
// the filter string
|
||||
private final String filter;
|
||||
|
||||
// the filter
|
||||
private VerboseFilter filter;
|
||||
// if we should notify the sender
|
||||
private final boolean notify;
|
||||
|
||||
// the number of checks we have processed
|
||||
private final AtomicInteger counter = new AtomicInteger(0);
|
||||
|
||||
// the number of checks we have processed and accepted, based on the filter rules for this
|
||||
// listener
|
||||
private final AtomicInteger matchedCounter = new AtomicInteger(0);
|
||||
|
||||
// the checks which passed the filter, up to a max size of #DATA_TRUNCATION
|
||||
private final List<CheckData> results = new ArrayList<>(DATA_TRUNCATION / 10);
|
||||
|
||||
public VerboseListener(String pluginVersion, Sender notifiedSender, String filter, boolean notify) {
|
||||
public VerboseListener(String pluginVersion, Sender notifiedSender, VerboseFilter filter, boolean notify) {
|
||||
this.pluginVersion = pluginVersion;
|
||||
this.notifiedSender = notifiedSender;
|
||||
this.filter = filter;
|
||||
@ -102,8 +98,8 @@ public class VerboseListener {
|
||||
// increment handled counter
|
||||
this.counter.incrementAndGet();
|
||||
|
||||
// check if the data passes our filters
|
||||
if (!VerboseFilter.passesFilter(data, this.filter)) {
|
||||
// check if the data passes our filter
|
||||
if (!this.filter.evaluate(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -117,49 +113,38 @@ public class VerboseListener {
|
||||
|
||||
// handle notifications
|
||||
if (this.notify) {
|
||||
StringBuilder msgContent = new StringBuilder();
|
||||
|
||||
if (this.notifiedSender.isConsole()) {
|
||||
msgContent.append("&8[&2")
|
||||
.append(data.getCheckOrigin().getCode())
|
||||
.append("&8] ");
|
||||
}
|
||||
|
||||
msgContent.append("&a")
|
||||
.append(data.getCheckTarget())
|
||||
.append("&7 - &a")
|
||||
.append(data.getPermission())
|
||||
.append("&7 - ")
|
||||
.append(getTristateColor(data.getResult()))
|
||||
.append(data.getResult().name().toLowerCase());
|
||||
|
||||
if (this.notifiedSender.isConsole()) {
|
||||
// just send as a raw message
|
||||
Message.VERBOSE_LOG.send(this.notifiedSender, msgContent.toString());
|
||||
} else {
|
||||
|
||||
// form a hoverevent from the check trace
|
||||
TextComponent textComponent = TextUtils.fromLegacy(Message.VERBOSE_LOG.asString(this.notifiedSender.getPlatform().getLocaleManager(), msgContent.toString()));
|
||||
|
||||
// build the text
|
||||
List<String> hover = new ArrayList<>();
|
||||
hover.add("&bOrigin: &2" + data.getCheckOrigin().name());
|
||||
hover.add("&bContext: &r" + CommandUtils.contextSetToString(data.getCheckContext()));
|
||||
hover.add("&bTrace: &r");
|
||||
|
||||
int overflow = readStack(data, 15, e -> hover.add("&7" + e.getClassName() + "." + e.getMethodName() + (e.getLineNumber() >= 0 ? ":" + e.getLineNumber() : "")));
|
||||
if (overflow != 0) {
|
||||
hover.add("&f... and " + overflow + " more");
|
||||
}
|
||||
|
||||
// send the message
|
||||
HoverEvent e = new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy(TextUtils.joinNewline(hover.stream()), CommandManager.AMPERSAND_CHAR));
|
||||
TextComponent msg = textComponent.toBuilder().applyDeep(comp -> comp.hoverEvent(e)).build();
|
||||
this.notifiedSender.sendMessage(msg);
|
||||
}
|
||||
sendNotification(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendNotification(CheckData data) {
|
||||
String msg = "&a" + data.getCheckTarget() + "&7 - &a" + data.getPermission() + "&7 - " + getTristateColor(data.getResult()) + data.getResult().name().toLowerCase();
|
||||
if (this.notifiedSender.isConsole()) {
|
||||
// just send as a raw message
|
||||
Message.VERBOSE_LOG.send(this.notifiedSender, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// form a hoverevent from the check trace
|
||||
TextComponent textComponent = TextUtils.fromLegacy(Message.VERBOSE_LOG.asString(this.notifiedSender.getPlatform().getLocaleManager(), msg));
|
||||
|
||||
// build the text
|
||||
List<String> hover = new ArrayList<>();
|
||||
hover.add("&bOrigin: &2" + data.getCheckOrigin().name());
|
||||
hover.add("&bContext: &r" + CommandUtils.contextSetToString(data.getCheckContext()));
|
||||
hover.add("&bTrace: &r");
|
||||
|
||||
int overflow = readStack(data, STACK_TRUNCATION_CHAT, e -> hover.add("&7" + e.getClassName() + "." + e.getMethodName() + (e.getLineNumber() >= 0 ? ":" + e.getLineNumber() : "")));
|
||||
if (overflow != 0) {
|
||||
hover.add("&f... and " + overflow + " more");
|
||||
}
|
||||
|
||||
// send the message
|
||||
HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy(TextUtils.joinNewline(hover.stream()), CommandManager.AMPERSAND_CHAR));
|
||||
TextComponent text = textComponent.toBuilder().applyDeep(comp -> comp.hoverEvent(hoverEvent)).build();
|
||||
this.notifiedSender.sendMessage(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads the captured data in this listener to a paste and returns the url
|
||||
*
|
||||
@ -177,11 +162,11 @@ public class VerboseListener {
|
||||
long secondsTaken = (now - this.startTime) / 1000L;
|
||||
String duration = DateUtil.formatTimeShort(secondsTaken);
|
||||
|
||||
String filter = this.filter;
|
||||
if (filter == null || filter.equals("")){
|
||||
String filter;
|
||||
if (this.filter.isBlank()){
|
||||
filter = "any";
|
||||
} else {
|
||||
filter = "`" + filter + "`";
|
||||
filter = "`" + this.filter.toString() + "`";
|
||||
}
|
||||
|
||||
// start building the message output
|
||||
@ -250,7 +235,7 @@ public class VerboseListener {
|
||||
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, 30, e -> prettyOutput.add(e.getClassName() + "." + e.getMethodName() + (e.getLineNumber() >= 0 ? ":" + e.getLineNumber() : "")));
|
||||
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");
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ public final class WebEditorUtils {
|
||||
try (OutputStream os = connection.getOutputStream()) {
|
||||
StringWriter sw = new StringWriter();
|
||||
new JsonWriter(sw).beginObject()
|
||||
.name("description").value("LuckPerms Web Permissions Editor Data")
|
||||
.name("description").value("LuckPerms Web Editor Data")
|
||||
.name("public").value(false)
|
||||
.name("files")
|
||||
.beginObject().name(FILE_NAME)
|
||||
|
Loading…
Reference in New Issue
Block a user