mirror of
https://github.com/LuckPerms/LuckPerms.git
synced 2024-10-31 07:51:05 +01:00
Cleanup config keys, make editor URL configurable
This commit is contained in:
parent
2e9561371d
commit
d4ac261e85
@ -36,10 +36,10 @@ import me.lucko.luckperms.common.commands.CommandException;
|
||||
import me.lucko.luckperms.common.commands.CommandResult;
|
||||
import me.lucko.luckperms.common.commands.abstraction.SubCommand;
|
||||
import me.lucko.luckperms.common.commands.sender.Sender;
|
||||
import me.lucko.luckperms.common.config.ConfigKeys;
|
||||
import me.lucko.luckperms.common.constants.Message;
|
||||
import me.lucko.luckperms.common.constants.Permission;
|
||||
import me.lucko.luckperms.common.core.NodeModel;
|
||||
import me.lucko.luckperms.common.core.PriorityComparator;
|
||||
import me.lucko.luckperms.common.core.model.Group;
|
||||
import me.lucko.luckperms.common.core.model.PermissionHolder;
|
||||
import me.lucko.luckperms.common.core.model.User;
|
||||
@ -56,13 +56,16 @@ import java.io.StringWriter;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class HolderEditor<T extends PermissionHolder> extends SubCommand<T> {
|
||||
private static final String USER_ID_PATTERN = "user/";
|
||||
private static final String GROUP_ID_PATTERN = "group/";
|
||||
private static final String FILE_NAME = "luckperms-data.json";
|
||||
|
||||
public HolderEditor(boolean user) {
|
||||
super("editor", "Opens the web permission editor", user ? Permission.USER_EDITOR : Permission.GROUP_EDITOR,
|
||||
Predicates.alwaysFalse(), null
|
||||
@ -83,8 +86,8 @@ public class HolderEditor<T extends PermissionHolder> extends SubCommand<T> {
|
||||
}
|
||||
|
||||
List<String> parts = Splitter.on('/').splitToList(dataUrl);
|
||||
String id = parts.get(4) + "/" + parts.get(6);
|
||||
String url = "https://lpedit.lucko.me/?" + id;
|
||||
String id = "?" + parts.get(4) + "/" + parts.get(6);
|
||||
String url = plugin.getConfiguration().get(ConfigKeys.WEB_EDITOR_URL_PATTERN) + id;
|
||||
|
||||
Message.EDITOR_URL.send(sender);
|
||||
sender.sendMessage(new FancyMessage(url).color(ChatColor.getByChar('b')).link(url));
|
||||
@ -94,10 +97,10 @@ public class HolderEditor<T extends PermissionHolder> extends SubCommand<T> {
|
||||
private static String id(PermissionHolder holder) {
|
||||
if (holder instanceof User) {
|
||||
User user = ((User) holder);
|
||||
return "user/" + user.getUuid().toString();
|
||||
return USER_ID_PATTERN + user.getUuid().toString();
|
||||
} else {
|
||||
Group group = ((Group) holder);
|
||||
return "group/" + group.getName();
|
||||
return GROUP_ID_PATTERN + group.getName();
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,7 +118,7 @@ public class HolderEditor<T extends PermissionHolder> extends SubCommand<T> {
|
||||
.name("description").value("LuckPerms Web Permissions Editor Data")
|
||||
.name("public").value(false)
|
||||
.name("files")
|
||||
.beginObject().name("luckperms-data.json")
|
||||
.beginObject().name(FILE_NAME)
|
||||
.beginObject().name("content").value(content)
|
||||
.endObject()
|
||||
.endObject()
|
||||
@ -131,7 +134,7 @@ public class HolderEditor<T extends PermissionHolder> extends SubCommand<T> {
|
||||
try (InputStream inputStream = connection.getInputStream()) {
|
||||
try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
|
||||
JsonObject response = new Gson().fromJson(reader, JsonObject.class);
|
||||
return response.get("files").getAsJsonObject().get("luckperms-data.json").getAsJsonObject().get("raw_url").getAsString();
|
||||
return response.get("files").getAsJsonObject().get(FILE_NAME).getAsJsonObject().get("raw_url").getAsString();
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,7 +153,7 @@ public class HolderEditor<T extends PermissionHolder> extends SubCommand<T> {
|
||||
}
|
||||
|
||||
private static JsonArray serializePermissions(Set<NodeModel> nodes) {
|
||||
List<JsonObject> data = new ArrayList<>();
|
||||
JsonArray arr = new JsonArray();
|
||||
|
||||
for (NodeModel node : nodes) {
|
||||
JsonObject attributes = new JsonObject();
|
||||
@ -173,17 +176,7 @@ public class HolderEditor<T extends PermissionHolder> extends SubCommand<T> {
|
||||
attributes.add("context", node.getContextsAsJson());
|
||||
}
|
||||
|
||||
data.add(attributes);
|
||||
}
|
||||
|
||||
data.sort((o1, o2) -> PriorityComparator.get().compareStrings(
|
||||
o1.get("permission").getAsString(),
|
||||
o2.get("permission").getAsString()
|
||||
));
|
||||
|
||||
JsonArray arr = new JsonArray();
|
||||
for (JsonObject o : data) {
|
||||
arr.add(o);
|
||||
arr.add(attributes);
|
||||
}
|
||||
|
||||
return arr;
|
||||
|
@ -57,21 +57,67 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* A class of static {@link ConfigKey}s used by the plugin.
|
||||
*/
|
||||
@UtilityClass
|
||||
public class ConfigKeys {
|
||||
|
||||
/**
|
||||
* The name of the server
|
||||
*/
|
||||
public static final ConfigKey<String> SERVER = LowercaseStringKey.of("server", "global");
|
||||
|
||||
/**
|
||||
* How many minutes to wait between syncs. A value <= 0 will disable syncing.
|
||||
*/
|
||||
public static final ConfigKey<Integer> SYNC_TIME = EnduringKey.wrap(IntegerKey.of("data.sync-minutes", -1));
|
||||
public static final ConfigKey<String> DEFAULT_GROUP_NODE = StaticKey.of("group.default"); // constant since 2.6
|
||||
public static final ConfigKey<String> DEFAULT_GROUP_NAME = StaticKey.of("default"); // constant since 2.6
|
||||
|
||||
/**
|
||||
* The permission node associated with the default group
|
||||
*
|
||||
* Constant since 2.6
|
||||
*/
|
||||
public static final ConfigKey<String> DEFAULT_GROUP_NODE = StaticKey.of("group.default");
|
||||
|
||||
/**
|
||||
* The name of the default group
|
||||
*
|
||||
* Constant since 2.6
|
||||
*/
|
||||
public static final ConfigKey<String> DEFAULT_GROUP_NAME = StaticKey.of("default");
|
||||
|
||||
/**
|
||||
* If permissions without a server context should be included.
|
||||
*/
|
||||
public static final ConfigKey<Boolean> INCLUDING_GLOBAL_PERMS = BooleanKey.of("include-global", true);
|
||||
|
||||
/**
|
||||
* If permissions without a world context should be included.
|
||||
*/
|
||||
public static final ConfigKey<Boolean> INCLUDING_GLOBAL_WORLD_PERMS = BooleanKey.of("include-global-world", true);
|
||||
|
||||
/**
|
||||
* If groups without a server context should be included.
|
||||
*/
|
||||
public static final ConfigKey<Boolean> APPLYING_GLOBAL_GROUPS = BooleanKey.of("apply-global-groups", true);
|
||||
|
||||
/**
|
||||
* If groups without a world context should be included.
|
||||
*/
|
||||
public static final ConfigKey<Boolean> APPLYING_GLOBAL_WORLD_GROUPS = BooleanKey.of("apply-global-world-groups", true);
|
||||
|
||||
/**
|
||||
* If the server provided uuids should be used. False if we should use the LP cache for existing users.
|
||||
*/
|
||||
public static final ConfigKey<Boolean> USE_SERVER_UUIDS = AbstractKey.of(c -> {
|
||||
// backwards compatible with the old online-mode option
|
||||
return c.contains("use-server-uuids") ? c.getBoolean("use-server-uuids", true) : c.getBoolean("online-mode", true);
|
||||
});
|
||||
|
||||
/**
|
||||
* Controls how temporary add commands should behave
|
||||
*/
|
||||
public static final ConfigKey<TemporaryModifier> TEMPORARY_ADD_BEHAVIOUR = AbstractKey.of(c -> {
|
||||
String option = c.getString("temporary-add-behaviour", "deny").toLowerCase();
|
||||
if (!option.equals("deny") && !option.equals("replace") && !option.equals("accumulate")) {
|
||||
@ -80,6 +126,10 @@ public class ConfigKeys {
|
||||
|
||||
return TemporaryModifier.valueOf(option.toUpperCase());
|
||||
});
|
||||
|
||||
/**
|
||||
* How primary groups should be calculated.
|
||||
*/
|
||||
public static final ConfigKey<String> PRIMARY_GROUP_CALCULATION_METHOD = EnduringKey.wrap(AbstractKey.of(c -> {
|
||||
String option = c.getString("primary-group-calculation", "stored").toLowerCase();
|
||||
if (!option.equals("stored") && !option.equals("parents-by-weight") && !option.equals("all-parents-by-weight")) {
|
||||
@ -88,6 +138,10 @@ public class ConfigKeys {
|
||||
|
||||
return option;
|
||||
}));
|
||||
|
||||
/**
|
||||
* A function to create primary group holder instances based upon the {@link #PRIMARY_GROUP_CALCULATION_METHOD} setting.
|
||||
*/
|
||||
public static final ConfigKey<Function<User, PrimaryGroupHolder>> PRIMARY_GROUP_CALCULATION = EnduringKey.wrap(AbstractKey.of(c -> {
|
||||
String option = PRIMARY_GROUP_CALCULATION_METHOD.get(c);
|
||||
switch (option) {
|
||||
@ -99,15 +153,55 @@ public class ConfigKeys {
|
||||
return (Function<User, PrimaryGroupHolder>) AllParentsByWeightHolder::new;
|
||||
}
|
||||
}));
|
||||
|
||||
/**
|
||||
* If wildcards are being applied
|
||||
*/
|
||||
public static final ConfigKey<Boolean> APPLYING_WILDCARDS = EnduringKey.wrap(BooleanKey.of("apply-wildcards", true));
|
||||
|
||||
/**
|
||||
* If regex permissions are being applied
|
||||
*/
|
||||
public static final ConfigKey<Boolean> APPLYING_REGEX = EnduringKey.wrap(BooleanKey.of("apply-regex", true));
|
||||
|
||||
/**
|
||||
* If shorthand permissions are being applied
|
||||
*/
|
||||
public static final ConfigKey<Boolean> APPLYING_SHORTHAND = EnduringKey.wrap(BooleanKey.of("apply-shorthand", true));
|
||||
|
||||
/**
|
||||
* If Bukkit child permissions are being applied. This setting is ignored on other platforms.
|
||||
*/
|
||||
public static final ConfigKey<Boolean> APPLY_BUKKIT_CHILD_PERMISSIONS = EnduringKey.wrap(BooleanKey.of("apply-bukkit-child-permissions", true));
|
||||
|
||||
/**
|
||||
* If Bukkit default permissions are being applied. This setting is ignored on other platforms.
|
||||
*/
|
||||
public static final ConfigKey<Boolean> APPLY_BUKKIT_DEFAULT_PERMISSIONS = EnduringKey.wrap(BooleanKey.of("apply-bukkit-default-permissions", true));
|
||||
|
||||
/**
|
||||
* If Bukkit attachment permissions are being applied. This setting is ignored on other platforms.
|
||||
*/
|
||||
public static final ConfigKey<Boolean> APPLY_BUKKIT_ATTACHMENT_PERMISSIONS = EnduringKey.wrap(BooleanKey.of("apply-bukkit-attachment-permissions", true));
|
||||
|
||||
/**
|
||||
* If BungeeCord configured permissions are being applied. This setting is ignored on other platforms.
|
||||
*/
|
||||
public static final ConfigKey<Boolean> APPLY_BUNGEE_CONFIG_PERMISSIONS = EnduringKey.wrap(BooleanKey.of("apply-bungee-config-permissions", false));
|
||||
|
||||
/**
|
||||
* If Sponge's implicit permission inheritance system should be applied
|
||||
*/
|
||||
public static final ConfigKey<Boolean> APPLY_SPONGE_IMPLICIT_WILDCARDS = EnduringKey.wrap(BooleanKey.of("apply-sponge-implicit-wildcards", true));
|
||||
|
||||
/**
|
||||
* If Sponge default subjects should be applied
|
||||
*/
|
||||
public static final ConfigKey<Boolean> APPLY_SPONGE_DEFAULT_SUBJECTS = EnduringKey.wrap(BooleanKey.of("apply-sponge-default-subjects", true));
|
||||
|
||||
/**
|
||||
* The configured group weightings
|
||||
*/
|
||||
public static final ConfigKey<Map<String, Integer>> GROUP_WEIGHTS = AbstractKey.of(c -> {
|
||||
return c.getMap("group-weight", ImmutableMap.of()).entrySet().stream().collect(ImmutableCollectors.toImmutableMap(
|
||||
e -> e.getKey().toLowerCase(),
|
||||
@ -120,6 +214,10 @@ public class ConfigKeys {
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a new prefix MetaStack element based upon the configured values. Be aware that this instance should be copied for each unique user.
|
||||
*/
|
||||
public static final ConfigKey<GenericMetaStack> PREFIX_FORMATTING_OPTIONS = AbstractKey.of(l -> {
|
||||
List<String> format = l.getList("meta-formatting.prefix.format", new ArrayList<>());
|
||||
if (format.isEmpty()) {
|
||||
@ -131,6 +229,10 @@ public class ConfigKeys {
|
||||
|
||||
return new GenericMetaStack(StackElementFactory.fromList(l.getPlugin(), format, true), startSpacer, middleSpacer, endSpacer);
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a new suffix MetaStack element based upon the configured values. Be aware that this instance should be copied for each unique user.
|
||||
*/
|
||||
public static final ConfigKey<GenericMetaStack> SUFFIX_FORMATTING_OPTIONS = AbstractKey.of(l -> {
|
||||
List<String> format = l.getList("meta-formatting.suffix.format", new ArrayList<>());
|
||||
if (format.isEmpty()) {
|
||||
@ -142,10 +244,30 @@ public class ConfigKeys {
|
||||
|
||||
return new GenericMetaStack(StackElementFactory.fromList(l.getPlugin(), format, false), startSpacer, middleSpacer, endSpacer);
|
||||
});
|
||||
|
||||
/**
|
||||
* If log notifications are enabled
|
||||
*/
|
||||
public static final ConfigKey<Boolean> LOG_NOTIFY = BooleanKey.of("log-notify", true);
|
||||
|
||||
/**
|
||||
* If auto op is enabled. Only used by the Bukkit platform.
|
||||
*/
|
||||
public static final ConfigKey<Boolean> AUTO_OP = EnduringKey.wrap(BooleanKey.of("auto-op", false));
|
||||
|
||||
/**
|
||||
* If server operators should be enabled. Only used by the Bukkit platform.
|
||||
*/
|
||||
public static final ConfigKey<Boolean> OPS_ENABLED = EnduringKey.wrap(AbstractKey.of(c -> !AUTO_OP.get(c) && c.getBoolean("enable-ops", true)));
|
||||
|
||||
/**
|
||||
* If server operators should be able to use LuckPerms commands by default. Only used by the Bukkit platform.
|
||||
*/
|
||||
public static final ConfigKey<Boolean> COMMANDS_ALLOW_OP = EnduringKey.wrap(BooleanKey.of("commands-allow-op", true));
|
||||
|
||||
/**
|
||||
* The name of the server to use for Vault.
|
||||
*/
|
||||
public static final ConfigKey<String> VAULT_SERVER = AbstractKey.of(c -> {
|
||||
// default to true for backwards compatibility
|
||||
if (c.getBoolean("use-vault-server", true)) {
|
||||
@ -154,15 +276,41 @@ public class ConfigKeys {
|
||||
return SERVER.get(c);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* If Vault should apply global permissions
|
||||
*/
|
||||
public static final ConfigKey<Boolean> VAULT_INCLUDING_GLOBAL = BooleanKey.of("vault-include-global", true);
|
||||
|
||||
/**
|
||||
* If any worlds provided with Vault lookups should be ignored
|
||||
*/
|
||||
public static final ConfigKey<Boolean> VAULT_IGNORE_WORLD = BooleanKey.of("vault-ignore-world", false);
|
||||
|
||||
/* controls the settings for Vault primary group overrides. Likely to be removed in later versions */
|
||||
public static final ConfigKey<Boolean> VAULT_PRIMARY_GROUP_OVERRIDES = BooleanKey.of("vault-primary-groups-overrides.enabled", false);
|
||||
public static final ConfigKey<Boolean> VAULT_PRIMARY_GROUP_OVERRIDES_CHECK_INHERITED = BooleanKey.of("vault-primary-groups-overrides.check-inherited-permissions", false);
|
||||
public static final ConfigKey<Boolean> VAULT_PRIMARY_GROUP_OVERRIDES_CHECK_EXISTS = BooleanKey.of("vault-primary-groups-overrides.check-group-exists", true);
|
||||
public static final ConfigKey<Boolean> VAULT_PRIMARY_GROUP_OVERRIDES_CHECK_MEMBER_OF = BooleanKey.of("vault-primary-groups-overrides.check-user-member-of", true);
|
||||
|
||||
/**
|
||||
* If Vault debug mode is enabled
|
||||
*/
|
||||
public static final ConfigKey<Boolean> VAULT_DEBUG = BooleanKey.of("vault-debug", false);
|
||||
|
||||
/**
|
||||
* The world rewrites map
|
||||
*/
|
||||
public static final ConfigKey<Map<String, String>> WORLD_REWRITES = MapKey.of("world-rewrite");
|
||||
|
||||
/**
|
||||
* The group name rewrites map
|
||||
*/
|
||||
public static final ConfigKey<Map<String, String>> GROUP_NAME_REWRITES = MapKey.of("group-name-rewrite");
|
||||
|
||||
/**
|
||||
* The default assignments being applied by the plugin
|
||||
*/
|
||||
public static final ConfigKey<List<Rule>> DEFAULT_ASSIGNMENTS = AbstractKey.of(c -> {
|
||||
return c.getObjectList("default-assignments", ImmutableList.of()).stream().map(name -> {
|
||||
String hasTrue = c.getString("default-assignments." + name + ".if.has-true", null);
|
||||
@ -174,6 +322,10 @@ public class ConfigKeys {
|
||||
return new Rule(hasTrue, hasFalse, lacks, give, take, pg);
|
||||
}).collect(ImmutableCollectors.toImmutableList());
|
||||
});
|
||||
|
||||
/**
|
||||
* The database settings, username, password, etc for use by any database
|
||||
*/
|
||||
public static final ConfigKey<DatastoreConfiguration> DATABASE_VALUES = EnduringKey.wrap(AbstractKey.of(c -> {
|
||||
return new DatastoreConfiguration(
|
||||
c.getString("data.address", null),
|
||||
@ -183,10 +335,30 @@ public class ConfigKeys {
|
||||
c.getInt("data.pool-size", 10)
|
||||
);
|
||||
}));
|
||||
|
||||
/**
|
||||
* The prefix for any SQL tables
|
||||
*/
|
||||
public static final ConfigKey<String> SQL_TABLE_PREFIX = EnduringKey.wrap(StringKey.of("data.table_prefix", "luckperms_"));
|
||||
|
||||
/**
|
||||
* The name of the storage method being used
|
||||
*/
|
||||
public static final ConfigKey<String> STORAGE_METHOD = EnduringKey.wrap(LowercaseStringKey.of("storage-method", "h2"));
|
||||
|
||||
/**
|
||||
* If storage files should be monitored for changes
|
||||
*/
|
||||
public static final ConfigKey<Boolean> WATCH_FILES = BooleanKey.of("watch-files", true);
|
||||
|
||||
/**
|
||||
* If split storage is being used
|
||||
*/
|
||||
public static final ConfigKey<Boolean> SPLIT_STORAGE = EnduringKey.wrap(BooleanKey.of("split-storage.enabled", false));
|
||||
|
||||
/**
|
||||
* The options for split storage
|
||||
*/
|
||||
public static final ConfigKey<Map<String, String>> SPLIT_STORAGE_OPTIONS = EnduringKey.wrap(AbstractKey.of(c -> {
|
||||
return ImmutableMap.<String, String>builder()
|
||||
.put("user", c.getString("split-storage.methods.user", "h2").toLowerCase())
|
||||
@ -196,32 +368,68 @@ public class ConfigKeys {
|
||||
.put("log", c.getString("split-storage.methods.log", "h2").toLowerCase())
|
||||
.build();
|
||||
}));
|
||||
|
||||
/**
|
||||
* The name of the messaging service in use, or "none" if not enabled.
|
||||
*/
|
||||
public static final ConfigKey<String> MESSAGING_SERVICE = EnduringKey.wrap(LowercaseStringKey.of("messaging-service", "none"));
|
||||
|
||||
/**
|
||||
* If updates should be automatically pushed by the messaging service
|
||||
*/
|
||||
public static final ConfigKey<Boolean> AUTO_PUSH_UPDATES = EnduringKey.wrap(BooleanKey.of("auto-push-updates", true));
|
||||
|
||||
/**
|
||||
* If redis messaging is enabled
|
||||
*/
|
||||
public static final ConfigKey<Boolean> REDIS_ENABLED = EnduringKey.wrap(BooleanKey.of("redis.enabled", false));
|
||||
|
||||
/**
|
||||
* The address of the redis server
|
||||
*/
|
||||
public static final ConfigKey<String> REDIS_ADDRESS = EnduringKey.wrap(StringKey.of("redis.address", null));
|
||||
|
||||
/**
|
||||
* The password in use by the redis server, or an empty string if there is no passworld
|
||||
*/
|
||||
public static final ConfigKey<String> REDIS_PASSWORD = EnduringKey.wrap(StringKey.of("redis.password", ""));
|
||||
|
||||
public static List<ConfigKey<?>> getAllKeys() {
|
||||
ImmutableList.Builder<ConfigKey<?>> keys = ImmutableList.builder();
|
||||
/**
|
||||
* The URL of the web editor
|
||||
*/
|
||||
public static final ConfigKey<String> WEB_EDITOR_URL_PATTERN = StringKey.of("", "https://lpedit.lucko.me/");
|
||||
|
||||
try {
|
||||
Field[] values = ConfigKeys.class.getFields();
|
||||
for (Field f : values) {
|
||||
if (!Modifier.isStatic(f.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
private static List<ConfigKey<?>> KEYS = null;
|
||||
|
||||
Object val = f.get(null);
|
||||
if (val instanceof ConfigKey<?>) {
|
||||
keys.add((ConfigKey<?>) val);
|
||||
/**
|
||||
* Gets all of the possible config keys defined in this class
|
||||
*
|
||||
* @return all of the possible config keys defined in this class
|
||||
*/
|
||||
public static synchronized List<ConfigKey<?>> getAllKeys() {
|
||||
if (KEYS == null) {
|
||||
ImmutableList.Builder<ConfigKey<?>> keys = ImmutableList.builder();
|
||||
|
||||
try {
|
||||
Field[] values = ConfigKeys.class.getFields();
|
||||
for (Field f : values) {
|
||||
if (!Modifier.isStatic(f.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object val = f.get(null);
|
||||
if (val instanceof ConfigKey<?>) {
|
||||
keys.add((ConfigKey<?>) val);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
||||
KEYS = keys.build();
|
||||
}
|
||||
|
||||
return keys.build();
|
||||
return KEYS;
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user