diff --git a/src/main/java/link/locutus/discord/apiv1/enums/ResourceType.java b/src/main/java/link/locutus/discord/apiv1/enums/ResourceType.java index ab098ceb..6cd5f5fd 100644 --- a/src/main/java/link/locutus/discord/apiv1/enums/ResourceType.java +++ b/src/main/java/link/locutus/discord/apiv1/enums/ResourceType.java @@ -169,6 +169,11 @@ public static Map parseResources(String arg, boolean allow if (MathMan.isInteger(arg)) { throw new IllegalArgumentException("Please use `$" + arg + "` or `money=" + arg + "` for money, not `" + arg + "`"); } + if (arg.contains(" AM ") || arg.contains(" PM ")) { + arg = arg.replaceAll("([0-9]{1,2}:[0-9]{2})[ ](AM|PM)", "") + .replace("\n", " ") + .replaceAll("[ ]+", " "); + } if (arg.contains("---+") && arg.contains("-+-")) { arg = arg.replace("-+-", "---"); int start = arg.indexOf("---+"); diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/binding/bindings/Placeholders.java b/src/main/java/link/locutus/discord/commands/manager/v2/binding/bindings/Placeholders.java index f93febd1..1ada2e3e 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/binding/bindings/Placeholders.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/binding/bindings/Placeholders.java @@ -36,6 +36,7 @@ import org.json.JSONObject; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashMap; @@ -689,7 +690,16 @@ private IllegalArgumentException throwUnknownCommand(String command) { } Placeholders placeholders = this; if (previousFunc != null) { - placeholders = Locutus.cmd().getV2().getPlaceholders().get((Class) previousFunc.getType()); + Type type = previousFunc.getType(); + if (type instanceof Class) { + placeholders = Locutus.cmd().getV2().getPlaceholders().get((Class) type); + } else { + TypedFunction result = handleParameterized(store, functionName, argumentString, previousFunc, depth, throwError); + if (result != null) { + previousFunc = result; + continue; + } + } if (placeholders == null) { throw new IllegalArgumentException("Cannot call `" + arg + "` on function: `" + previousFunc.getName() + "` as return type is not public: `" + ((Class) previousFunc.getType()).getSimpleName() + "`"); } @@ -730,6 +740,34 @@ private IllegalArgumentException throwUnknownCommand(String command) { return previousFunc; } + private TypedFunction handleParameterized(ValueStore store, String functionName, String argumentString, TypedFunction previousFunc, int depth, boolean throwError) { + if (argumentString != null && !argumentString.isEmpty()) return null; + Type type = previousFunc.getType(); + if (!(type instanceof ParameterizedType pt)) return null; + Type rawType = pt.getRawType(); + if (!(rawType instanceof Class rawClass)) return null; +// if (Map.class.isAssignableFrom(rawClass)) { + if (!rawClass.isAssignableFrom(Map.class)) return null; + Type[] args = pt.getActualTypeArguments(); + Type keyType = args[0]; + Type valueType = args[1]; + if (!(keyType instanceof Class keyClass && keyClass.isEnum())) return null; + // get enum options + Enum[] enumConstants = (Enum[]) keyClass.getEnumConstants(); + // if any enum constant equals ignore case + for (Enum enumConstant : enumConstants) { + if (enumConstant.name().equalsIgnoreCase(functionName)) { + Function getValue = f -> { + Object key = enumConstant; + Map map = (Map) previousFunc.applyCached(f); + return map.get(key); + }; + return TypedFunction.createParent(valueType, getValue, functionName, previousFunc); + } + } + return null; + } + private TypedFunction format(ValueStore store, ParametricCallable command, Map> arguments) { Map resolvedArgs = new Object2ObjectLinkedOpenHashMap<>(); Map> resolvedByEntity = new Object2ObjectLinkedOpenHashMap<>(); diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/CommandManager2.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/CommandManager2.java index 7ab2686f..40a0f94f 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/CommandManager2.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/CommandManager2.java @@ -238,11 +238,13 @@ public static Map parseArguments(Set params, String inpu public CommandManager2 registerDefaults() { // this.commands.registerMethod(new TestCommands(), List.of("test"), "test", "test"); - getCommands().registerMethod(new AdminCommands(), List.of("admin", "unset"), "unsetNews", "settings"); - getCommands().registerMethod(new AdminCommands(), List.of("admin", "unset"), "unsetKeys", "news"); + getCommands().registerMethod(new AdminCommands(), List.of("admin", "settings"), "unsetNews", "subscribe"); + getCommands().registerMethod(new AdminCommands(), List.of("admin", "settings"), "unsetKeys", "unset"); + getCommands().registerMethod(new AdminCommands(), List.of("admin", "settings"), "infoBulk", "info_servers"); + getCommands().registerMethod(new WarCommands(), List.of("alerts", "beige"), "testBeigeAlertAuto", "test_auto"); getCommands().registerMethod(new UtilityCommands(), List.of("nation", "history"), "vmHistory", "vm"); - getCommands().registerMethod(new UtilityCommands(), List.of("nation", "history"), "gray_streak", "gray_streak"); + getCommands().registerMethod(new UtilityCommands(), List.of("nation", "history"), "grayStreak", "gray_streak"); getCommands().registerMethod(new UtilityCommands(), List.of("tax"), "setBracketBulk", "set_from_sheet"); getCommands().registerMethod(new StatCommands(), List.of("stats_other", "global_metrics"), "orbisStatByDay", "by_time"); diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/binding/PWBindings.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/binding/PWBindings.java index 154fcae0..29e79367 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/binding/PWBindings.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/binding/PWBindings.java @@ -50,6 +50,7 @@ import link.locutus.discord.db.entities.newsletter.NewsletterManager; import link.locutus.discord.db.guild.GuildSetting; import link.locutus.discord.db.guild.GuildKey; +import link.locutus.discord.db.guild.GuildSettingCategory; import link.locutus.discord.event.mail.MailReceivedEvent; import link.locutus.discord.pnw.AllianceList; import link.locutus.discord.pnw.BeigeReason; @@ -229,6 +230,11 @@ public DBLoan.Status LoanStatus(String input) { return emum(DBLoan.Status.class, input); } + @Binding(value = "The guild setting category") + public GuildSettingCategory GuildSettingCategory(String input) { + return emum(GuildSettingCategory.class, input); + } + @Binding(value = "The success type of an attack") public SuccessType SuccessType(String input) { return emum(SuccessType.class, input); diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/AdminCommands.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/AdminCommands.java index 906d4b2e..eaade49b 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/AdminCommands.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/AdminCommands.java @@ -84,6 +84,7 @@ import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; +import net.dv8tion.jda.api.entities.channel.unions.DefaultGuildChannelUnion; import net.dv8tion.jda.api.exceptions.InsufficientPermissionException; import org.json.JSONObject; import org.jsoup.Jsoup; @@ -104,6 +105,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -410,6 +412,7 @@ public String unsetKeys(@Me IMessageIO io, @Me JSONObject command, @Switch("g") boolean unset_invalid_aa, @Switch("a") boolean unset_all, @Switch("v") boolean unset_validate, + @Switch("m") String unsetMessage, @Switch("f") boolean force) { Map>> unsetReasons = new LinkedHashMap<>(); @@ -424,6 +427,29 @@ public String unsetKeys(@Me IMessageIO io, @Me JSONObject command, } } for (GuildDB otherDb : guilds) { + Map isUnset = new LinkedHashMap<>(); + BiConsumer unset = (setting, reason) -> { + if (force) { + if (isUnset.getOrDefault(setting, false)) return; + isUnset.put(setting, true); + String previousValue = setting.getRaw(otherDb, false); + Object value = setting.getOrNull(otherDb, false); + + otherDb.deleteInfo(setting); + + String message = reason + ": " + (unsetMessage == null ? "" : unsetMessage) + "\nPrevious value: `" + previousValue + "`"; + TextChannel sendTo = null; + if (value instanceof TextChannel tc && tc.canTalk()) sendTo = tc; + if (sendTo == null) sendTo = otherDb.getNotifcationChannel(); + if (sendTo != null) { + try { + RateLimitUtil.queue(sendTo.sendMessage(message)); + } catch (Exception ignore) { + } + } + } + }; + Guild otherGuild = otherDb.getGuild(); Map> byGuild = unsetReasons.computeIfAbsent(otherGuild, k -> new LinkedHashMap<>()); // only channel modes @@ -432,7 +458,7 @@ public String unsetKeys(@Me IMessageIO io, @Me JSONObject command, if (channel == null) continue; if (unset_cant_talk) { if (!channel.canTalk()) { - if (force) otherDb.deleteInfo(setting); + if (force) unset.accept(setting, "No Talk Permissions in " + channel.getAsMention()); byGuild.computeIfAbsent(setting, k -> new LinkedHashSet<>()).add("Can't talk"); continue; } @@ -449,7 +475,7 @@ public String unsetKeys(@Me IMessageIO io, @Me JSONObject command, Object value = setting.getOrNull(otherDb); if (unset_null) { if (value == null) { - if (force) otherDb.deleteInfo(setting); + if (force) unset.accept(setting, "Invalid value (null)"); byGuild.computeIfAbsent(setting, k -> new LinkedHashSet<>()).add("Null"); continue; } @@ -463,13 +489,13 @@ public String unsetKeys(@Me IMessageIO io, @Me JSONObject command, notAllowedReason = e.getMessage(); } if (!allowed) { - if (force) otherDb.deleteInfo(setting); + if (force) unset.accept(setting, notAllowedReason); byGuild.computeIfAbsent(setting, k -> new LinkedHashSet<>()).add(notAllowedReason); continue; } } if (unset_validate) { - String validateReason = "Invalid"; + String validateReason = "Invalid Value (validation error)"; boolean valid = false; try { setting.validate(otherDb, null, value); @@ -478,7 +504,7 @@ public String unsetKeys(@Me IMessageIO io, @Me JSONObject command, validateReason = e.getMessage(); } if (!valid) { - if (force) otherDb.deleteInfo(setting); + if (force) unset.accept(setting, validateReason); byGuild.computeIfAbsent(setting, k -> new LinkedHashSet<>()).add(validateReason); continue; } @@ -486,13 +512,15 @@ public String unsetKeys(@Me IMessageIO io, @Me JSONObject command, } if (unset_invalid_aa) { if (!otherDb.isValidAlliance()) { - if (force) otherDb.deleteInfo(setting); +// if (force) otherDb.deleteInfo(setting); + if (force) unset.accept(setting, "No valid Alliance registered"); byGuild.computeIfAbsent(setting, k -> new LinkedHashSet<>()).add("Invalid AA"); continue; } } if (unset_all) { - if (force) otherDb.deleteInfo(setting); +// if (force) otherDb.deleteInfo(setting); + if (force) unset.accept(setting, "Setting removed by administrator."); byGuild.computeIfAbsent(setting, k -> new LinkedHashSet<>()).add("All"); } } @@ -527,7 +555,75 @@ public String unsetKeys(@Me IMessageIO io, @Me JSONObject command, return null; } - // todo unset invalid + @Command + @RolePermission(value = Roles.ADMIN, root = true) + public String infoBulk(@Me GuildDB db, @Me IMessageIO io, GuildSetting setting, Set guilds, @Switch("s") SpreadSheet sheet) throws GeneralSecurityException, IOException { + if (sheet == null) { + sheet = SpreadSheet.create(db, SheetKey.SETTINGS_SERVERS); + } + + // admin bulk_setting key + //- include alliances registered column (alliance ids) + //- have column for coalitions guild is in + //- have column for isValidAlliance + //- have column for number of active members 7200 in the alliance + + List header = new ArrayList<>(Arrays.asList( + "guild", + "guild_id", + "owner", + "owner_id", + "setting", + "raw", + "readable", + "is_invalid", + "lack_perms", + "root_col", + "alliances", + "aa_valid", + "active_aa_members" + )); + + sheet.setHeader(header); + + GuildDB root = Locutus.imp().getRootDb(); + + + for (GuildDB otherDb : guilds) { + String raw = setting.getRaw(otherDb, false); + if (raw == null) continue; + Object value = setting.getOrNull(otherDb, false); + String readable = value == null ? "![NULL]" : setting.toReadableString(db, value); + boolean noPerms = !setting.allowed(db); + + header.set(0, otherDb.getGuild().getName()); + header.set(1, otherDb.getIdLong() + ""); + long ownerId = otherDb.getGuild().getOwnerIdLong(); + header.set(2, DiscordUtil.getUserName(ownerId)); + header.set(3, ownerId + ""); + header.set(4, setting.name()); + header.set(5, raw); + header.set(6, readable); + header.set(7, value == null ? "true" : "false"); + header.set(8, noPerms ? "true" : "false"); + Set hasOnRoot = Arrays.stream(Coalition.values()).filter(otherDb::hasCoalitionPermsOnRoot).collect(Collectors.toSet()); + header.set(9, hasOnRoot.stream().map(Coalition::name).collect(Collectors.joining(","))); + Set aaIds = otherDb.getAllianceIds(); + header.set(10, aaIds == null ? "" : aaIds.toString()); + header.set(11, otherDb.isValidAlliance() ? "true" : "false"); + AllianceList aaList = otherDb.getAllianceList(); + int activeMembers = aaList == null ? -1 : aaList.getNations(true, 7200, true).size(); + header.set(12, activeMembers + ""); + + sheet.addRow(header); + } + + sheet.updateClearCurrentTab(); + sheet.updateWrite(); + + sheet.attach(io.create(), "setting_servers").send(); + return null; + } @Command @RolePermission(value = Roles.ADMIN, root = true) diff --git a/src/main/java/link/locutus/discord/db/GuildDB.java b/src/main/java/link/locutus/discord/db/GuildDB.java index f36e0bf7..b485de11 100644 --- a/src/main/java/link/locutus/discord/db/GuildDB.java +++ b/src/main/java/link/locutus/discord/db/GuildDB.java @@ -7,11 +7,12 @@ import link.locutus.discord.apiv1.enums.AccessType; import link.locutus.discord.apiv1.enums.DepositType; import link.locutus.discord.apiv3.enums.AlliancePermission; +import link.locutus.discord.commands.manager.v2.binding.annotation.Command; import link.locutus.discord.commands.manager.v2.binding.annotation.Me; +import link.locutus.discord.commands.manager.v2.impl.discord.permission.RolePermission; import link.locutus.discord.commands.manager.v2.impl.pw.refs.CM; import link.locutus.discord.commands.manager.v2.impl.pw.NationFilter; import link.locutus.discord.commands.war.WarCategory; -import link.locutus.discord.commands.manager.Command; import link.locutus.discord.commands.manager.v2.impl.pw.TaxRate; import link.locutus.discord.commands.rankings.builder.RankBuilder; import link.locutus.discord.config.Settings; @@ -58,6 +59,7 @@ import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.channel.concrete.Category; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.unions.DefaultGuildChannelUnion; @@ -3159,4 +3161,23 @@ public Role getRole(Roles role, Long allianceOrNull) { } return null; } + + /// + + @Command + public TextChannel getNotifcationChannel() { + TextChannel sendTo = guild.getSystemChannel(); + if (sendTo != null && !sendTo.canTalk()) sendTo = null; + if (sendTo == null) sendTo = guild.getCommunityUpdatesChannel(); + if (sendTo != null && !sendTo.canTalk()) sendTo = null; + if (sendTo == null) sendTo = guild.getRulesChannel(); + if (sendTo != null && !sendTo.canTalk()) sendTo = null; + if (sendTo == null) { + DefaultGuildChannelUnion df = guild.getDefaultChannel(); + if (df != null && df instanceof TextChannel tc && tc.canTalk()) { + sendTo = tc; + } + } + return sendTo; + } } \ No newline at end of file diff --git a/src/main/java/link/locutus/discord/db/NationDB.java b/src/main/java/link/locutus/discord/db/NationDB.java index 5fcf7a8b..63629914 100644 --- a/src/main/java/link/locutus/discord/db/NationDB.java +++ b/src/main/java/link/locutus/discord/db/NationDB.java @@ -2704,6 +2704,23 @@ public synchronized void createTreasuresDB(Collection treasures) { } } + public int countTreasures(int allianceId) { + synchronized (nationsByAlliance) { + Map nations = nationsByAlliance.get(allianceId); + if (nations == null || nations.isEmpty()) return 0; + int count = 0; + for (DBNation nation : nations.values()) { + if (nation.getPositionEnum().id <= Rank.APPLICANT.id || nation.getVm_turns() == 0) continue; + synchronized (treasuresByNation) { + Set treasures = treasuresByNation.get(nation.getId()); + if (treasures == null) continue; + count += treasures.size(); + } + } + return count; + } + } + public Set getTreasure(int nationId) { synchronized (treasuresByNation) { Set treasures = treasuresByNation.get(nationId); diff --git a/src/main/java/link/locutus/discord/db/entities/DBAlliance.java b/src/main/java/link/locutus/discord/db/entities/DBAlliance.java index 9c053c16..edce3f74 100644 --- a/src/main/java/link/locutus/discord/db/entities/DBAlliance.java +++ b/src/main/java/link/locutus/discord/db/entities/DBAlliance.java @@ -96,13 +96,8 @@ public DBAlliance(DBAlliance other) { @Command(desc = "Number of treasures in the alliance") public int getNumTreasures() { - int num = 0; - for (DBNation nation : getNations()) { - if (nation.getVm_turns() == 0) { - num += nation.getTreasures().size(); - } - } - return num; + if (allianceId == 0) return 0; + return Locutus.imp().getNationDB().countTreasures(allianceId); } @Command(desc = "Treasure bonus (decimal percent between 0-1)") diff --git a/src/main/java/link/locutus/discord/db/entities/DBNation.java b/src/main/java/link/locutus/discord/db/entities/DBNation.java index 9f9d6e1a..e8799ea1 100644 --- a/src/main/java/link/locutus/discord/db/entities/DBNation.java +++ b/src/main/java/link/locutus/discord/db/entities/DBNation.java @@ -3425,13 +3425,14 @@ public double equilibriumTaxRate(boolean updateNewCities, boolean force) { public double[] getRevenue() { return getRevenue(12); } + public double[] getRevenue(int turns) { return getRevenue(turns, true, true, true, true, false, false, getTreasureBonusPct(), false); } @Command(desc = "Treasure bonus decimal percent") public double getTreasureBonusPct() { - if (alliance_id == 0 || getVm_turns() > 0) return 0; + if (alliance_id == 0 || getPositionEnum().id < Rank.APPLICANT.id || getVm_turns() > 0) return 0; DBAlliance aa = getAlliance(); int treasures = aa.getNumTreasures(); Set natTreasures = getTreasures(); @@ -6527,4 +6528,20 @@ public Member getMember(GuildDB db) { } return null; } + + @Command(desc = "Daily revenue of a nation") + public Map revenue(@Default Integer turns, + @Switch("c") boolean no_cities, + @Switch("m") boolean no_military, + @Switch("t") boolean no_trade_bonus, + @Switch("b") boolean no_new_bonus, + @Switch("f") boolean no_food, + @Switch("p") boolean no_power, + @Switch("r") Double treasure_bonus) { + if (turns == null) turns = 12; + if (treasure_bonus == null) treasure_bonus = getTreasureBonusPct(); + double[] rss = getRevenue(turns, !no_cities, !no_military, !no_trade_bonus, !no_new_bonus, no_food, no_power, treasure_bonus, false); + System.out.println(ResourceType.toString(rss)); + return ResourceType.resourcesToMap(rss); + } } diff --git a/src/main/java/link/locutus/discord/db/guild/GuildSetting.java b/src/main/java/link/locutus/discord/db/guild/GuildSetting.java index 5e7b4e74..9bb09fbc 100644 --- a/src/main/java/link/locutus/discord/db/guild/GuildSetting.java +++ b/src/main/java/link/locutus/discord/db/guild/GuildSetting.java @@ -8,10 +8,13 @@ import link.locutus.discord.commands.manager.v2.binding.Parser; import link.locutus.discord.commands.manager.v2.binding.ValueStore; import link.locutus.discord.commands.manager.v2.binding.annotation.Command; +import link.locutus.discord.commands.manager.v2.binding.annotation.Default; import link.locutus.discord.commands.manager.v2.binding.annotation.Me; +import link.locutus.discord.commands.manager.v2.binding.annotation.Switch; import link.locutus.discord.commands.manager.v2.command.ParameterData; import link.locutus.discord.commands.manager.v2.command.ParametricCallable; import link.locutus.discord.commands.manager.v2.impl.SlashCommandManager; +import link.locutus.discord.commands.manager.v2.impl.discord.permission.RolePermission; import link.locutus.discord.commands.manager.v2.impl.pw.refs.CM; import link.locutus.discord.config.Settings; import link.locutus.discord.db.GuildDB; @@ -114,6 +117,7 @@ public ValueStore getStore() { } + @Command(desc = "The setting usage instructions") public abstract String help(); public abstract String toString(T value); @@ -197,6 +201,7 @@ private String getCommandObjRaw(GuildDB db, Object value) { return getCommandMention(); } + @Command(desc = "The setting command mention") public String getCommandMention() { if (Locutus.imp() == null || Locutus.imp().getSlashCommands() == null) { return CM.settings.info.cmd.key(name).toSlashCommand(); @@ -236,10 +241,12 @@ public T parse(GuildDB db, String input) { } } + @Command(desc = "The setting name and help instructions") public String toString() { return name() + "\n> " + help() + "\n"; } + @Command(desc = "The setting name") public String name() { if (name == null) { throw new IllegalStateException("Name is null for " + getClass().getSimpleName()); @@ -588,11 +595,32 @@ public String getName() { return name; } - @Command + @Command(desc = "The human readable representation of the value") + @RolePermission(Roles.ADMIN) public String getValueString(@Me GuildDB db) { T value = getOrNull(db); if (value == null) return null; return toReadableString(db, value); } + @Command(desc = "Does the setting have a value (even if invalid)") + @RolePermission(Roles.ADMIN) + public boolean hasValue(@Me GuildDB db, @Switch("d") boolean checkDelegate) { + return getOrNull(db, checkDelegate) != null; + } + + @Command(desc = "The setting value") + @RolePermission(Roles.ADMIN) + public T getValue(@Me GuildDB db, @Switch("d") boolean checkDelegate) { + return getOrNull(db, checkDelegate); + } + + @Command(desc = "If the value is invalid") + @RolePermission(Roles.ADMIN) + public boolean hasInvalidValue(@Me GuildDB db, @Switch("d") boolean checkDelegate) { + String raw = getRaw(db, checkDelegate); + if (raw == null) return false; + return getOrNull(db, checkDelegate) == null; + } + } diff --git a/src/main/java/link/locutus/discord/db/guild/SheetKey.java b/src/main/java/link/locutus/discord/db/guild/SheetKey.java index 6dba325b..438a1f1a 100644 --- a/src/main/java/link/locutus/discord/db/guild/SheetKey.java +++ b/src/main/java/link/locutus/discord/db/guild/SheetKey.java @@ -59,6 +59,7 @@ public enum SheetKey { MILITARY_RANKING, FORUM_PROFILES, INACTIVITY_STREAK, + SETTINGS_SERVERS, }