From 9fba895088bfc417fd2ee135afa27bff22bdb5f0 Mon Sep 17 00:00:00 2001 From: Taah Date: Mon, 23 May 2022 22:44:08 -0700 Subject: [PATCH] Begin working on permissions implementation. I don't know where every usage is but I've done a generalized implementation so far for commands. --- pom.xml | 47 +++++++- .../totalfreedommod/TotalFreedomMod.java | 26 ++++- .../totalfreedommod/admin/AdminList.java | 46 ++++++-- .../command/CommandPermissions.java | 3 + .../command/FreedomCommand.java | 43 ++++++++ .../totalfreedommod/config/ConfigEntry.java | 3 + .../httpd/module/Module_list.java | 1 + .../handler/DefaultPermissionHandler.java | 43 ++++++++ .../handler/IPermissionHandler.java | 20 ++++ .../handler/NMPermissionHandler.java | 95 ++++++++++++++++ .../handler/VaultPermissionHandler.java | 103 ++++++++++++++++++ src/main/resources/config.yml | 6 + 12 files changed, 416 insertions(+), 20 deletions(-) create mode 100644 src/main/java/me/totalfreedom/totalfreedommod/permissions/handler/DefaultPermissionHandler.java create mode 100644 src/main/java/me/totalfreedom/totalfreedommod/permissions/handler/IPermissionHandler.java create mode 100644 src/main/java/me/totalfreedom/totalfreedommod/permissions/handler/NMPermissionHandler.java create mode 100644 src/main/java/me/totalfreedom/totalfreedommod/permissions/handler/VaultPermissionHandler.java diff --git a/pom.xml b/pom.xml index 0f84a6fe..efea8ed3 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,17 @@ - + + + jitpack.io + https://jitpack.io + + + + networkmanager-repo + https://repo.networkmanager.xyz/repository/maven-public/ + + atlas-nexus-01-totalfreedom-development https://nexus-01.core.atlas-media.co.uk/repository/totalfreedom-development/ @@ -100,8 +110,8 @@ esentialsx-repo https://repo.essentialsx.net/releases/ - - + + @@ -189,7 +199,7 @@ v1.9 provided - + net.essentialsx EssentialsX @@ -231,19 +241,44 @@ 3.1.2 compile - + org.junit.jupiter junit-jupiter 5.8.0 compile - + org.eclipse.sisu org.eclipse.sisu.inject 0.3.4 + + + com.github.MilkBowl + VaultAPI + 1.7 + provided + + + org.bstats + bstats + + + + + nl.chimpgamer.networkmanager + api + 2.11.4 + provided + + + io.github.slimjar + slimjar + + + diff --git a/src/main/java/me/totalfreedom/totalfreedommod/TotalFreedomMod.java b/src/main/java/me/totalfreedom/totalfreedommod/TotalFreedomMod.java index 31dff85b..29f755b9 100644 --- a/src/main/java/me/totalfreedom/totalfreedommod/TotalFreedomMod.java +++ b/src/main/java/me/totalfreedom/totalfreedommod/TotalFreedomMod.java @@ -3,6 +3,7 @@ package me.totalfreedom.totalfreedommod; import java.io.File; import java.io.InputStream; import java.util.Properties; + import me.totalfreedom.totalfreedommod.admin.ActivityLog; import me.totalfreedom.totalfreedommod.admin.AdminList; import me.totalfreedom.totalfreedommod.banning.BanManager; @@ -35,6 +36,10 @@ import me.totalfreedom.totalfreedommod.fun.Trailer; import me.totalfreedom.totalfreedommod.httpd.HTTPDaemon; import me.totalfreedom.totalfreedommod.permissions.PermissionConfig; import me.totalfreedom.totalfreedommod.permissions.PermissionManager; +import me.totalfreedom.totalfreedommod.permissions.handler.DefaultPermissionHandler; +import me.totalfreedom.totalfreedommod.permissions.handler.IPermissionHandler; +import me.totalfreedom.totalfreedommod.permissions.handler.NMPermissionHandler; +import me.totalfreedom.totalfreedommod.permissions.handler.VaultPermissionHandler; import me.totalfreedom.totalfreedommod.player.PlayerList; import me.totalfreedom.totalfreedommod.punishments.PunishmentList; import me.totalfreedom.totalfreedommod.rank.RankManager; @@ -132,6 +137,8 @@ public class TotalFreedomMod extends JavaPlugin public WorldEditBridge web; public WorldGuardBridge wgb; + public IPermissionHandler permissionHandler; + public static TotalFreedomMod getPlugin() { return plugin; @@ -143,7 +150,7 @@ public class TotalFreedomMod extends JavaPlugin { if (plugin.getName().equalsIgnoreCase(pluginName)) { - return (TotalFreedomMod)plugin; + return (TotalFreedomMod) plugin; } } return null; @@ -208,6 +215,20 @@ public class TotalFreedomMod extends JavaPlugin // Metrics @ https://bstats.org/plugin/bukkit/TotalFreedomMod/2966 new Metrics(this, 2966); + + if (getServer().getPluginManager().isPluginEnabled("NetworkManager")) + { + FLog.info("Using NetworkManager's permission handling"); + this.permissionHandler = new NMPermissionHandler(this); + } else if (getServer().getPluginManager().isPluginEnabled("Vault")) + { + FLog.info("Using Vault's permission handling"); + this.permissionHandler = new VaultPermissionHandler(this); + } else + { + FLog.info("Using Bukkit's native permission handling"); + this.permissionHandler = new DefaultPermissionHandler(); + } } @Override @@ -255,8 +276,7 @@ public class TotalFreedomMod extends JavaPlugin date = props.getProperty("buildDate", "unknown"); // Need to do this or it will display ${git.commit.id.abbrev} head = props.getProperty("buildHead", "unknown").replace("${git.commit.id.abbrev}", "unknown"); - } - catch (Exception ex) + } catch (Exception ex) { FLog.severe("Could not load build properties! Did you compile with NetBeans/Maven?"); FLog.severe(ex); diff --git a/src/main/java/me/totalfreedom/totalfreedommod/admin/AdminList.java b/src/main/java/me/totalfreedom/totalfreedommod/admin/AdminList.java index 639023b8..6e13831e 100644 --- a/src/main/java/me/totalfreedom/totalfreedommod/admin/AdminList.java +++ b/src/main/java/me/totalfreedom/totalfreedommod/admin/AdminList.java @@ -2,17 +2,22 @@ package me.totalfreedom.totalfreedommod.admin; import com.google.common.collect.Maps; import com.google.common.collect.Sets; + import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; import java.util.concurrent.TimeUnit; + import me.totalfreedom.totalfreedommod.FreedomService; import me.totalfreedom.totalfreedommod.config.ConfigEntry; +import me.totalfreedom.totalfreedommod.permissions.handler.NMPermissionHandler; +import me.totalfreedom.totalfreedommod.permissions.handler.VaultPermissionHandler; import me.totalfreedom.totalfreedommod.rank.Rank; import me.totalfreedom.totalfreedommod.util.FLog; import me.totalfreedom.totalfreedommod.util.FUtil; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; public class AdminList extends FreedomService @@ -55,8 +60,7 @@ public class AdminList extends FreedomService allAdmins.add(admin); } } - } - catch (SQLException e) + } catch (SQLException e) { FLog.severe("Failed to load admin list: " + e.getMessage()); } @@ -110,7 +114,7 @@ public class AdminList extends FreedomService return true; } - Admin admin = getAdmin((Player)sender); + Admin admin = getAdmin((Player) sender); return admin != null && admin.isActive(); } @@ -121,28 +125,48 @@ public class AdminList extends FreedomService { return true; } + if (plugin.permissionHandler instanceof NMPermissionHandler || plugin.permissionHandler instanceof VaultPermissionHandler) + { + FLog.debug("Using " + plugin.permissionHandler.getClass().getSimpleName() + " for AdminList#isAdmin"); + return plugin.permissionHandler.inGroup(player, ConfigEntry.PERMISSIONS_GROUPS_SENIOR.getString()); + } + FLog.debug("AdminList#isAdmin: Returning false because there is no permissions plugin that supports groups on the server"); + return false; - Admin admin = getAdmin(player); + /*Admin admin = getAdmin(player); - return admin != null && admin.isActive(); + return admin != null && admin.isActive();*/ } public boolean isSeniorAdmin(CommandSender sender) { - Admin admin = getAdmin(sender); + //TODO: BukkitTelnet checks, but fuck that plugin + if (sender instanceof ConsoleCommandSender) + { + return true; + } + if (sender instanceof Player player && (plugin.permissionHandler instanceof NMPermissionHandler || plugin.permissionHandler instanceof VaultPermissionHandler)) + { + FLog.debug("Using " + plugin.permissionHandler.getClass().getSimpleName() + " for AdminList#isSeniorAdmin"); + return plugin.permissionHandler.inGroup(player, ConfigEntry.PERMISSIONS_GROUPS_SENIOR.getString()); + } + FLog.debug("AdminList#isSeniorAdmin: Returning false because there is no permissions plugin that supports groups on the server"); + return false; +// return plugin.permissionHandler.hasPermission(sender, "totalfreedommod.admin"); + /*Admin admin = getAdmin(sender); if (admin == null) { return false; } - return admin.getRank().ordinal() >= Rank.SENIOR_ADMIN.ordinal(); + return admin.getRank().ordinal() >= Rank.SENIOR_ADMIN.ordinal();*/ } public Admin getAdmin(CommandSender sender) { if (sender instanceof Player) { - return getAdmin((Player)sender); + return getAdmin((Player) sender); } return getEntryByName(sender.getName()); @@ -271,13 +295,13 @@ public class AdminList extends FreedomService ResultSet currentSave = plugin.sql.getAdminByUuid(admin.getUuid()); for (Map.Entry entry : admin.toSQLStorable().entrySet()) { - Object storedValue = plugin.sql.getValue(currentSave, entry.getKey(), entry.getValue()); if (storedValue != null && !storedValue.equals(entry.getValue()) || storedValue == null && entry.getValue() != null || entry.getValue() == null) + Object storedValue = plugin.sql.getValue(currentSave, entry.getKey(), entry.getValue()); + if (storedValue != null && !storedValue.equals(entry.getValue()) || storedValue == null && entry.getValue() != null || entry.getValue() == null) { plugin.sql.setAdminValue(admin, entry.getKey(), entry.getValue()); } } - } - catch (SQLException e) + } catch (SQLException e) { FLog.severe("Failed to save admin: " + e.getMessage()); } diff --git a/src/main/java/me/totalfreedom/totalfreedommod/command/CommandPermissions.java b/src/main/java/me/totalfreedom/totalfreedommod/command/CommandPermissions.java index 3db48004..393b24b0 100644 --- a/src/main/java/me/totalfreedom/totalfreedommod/command/CommandPermissions.java +++ b/src/main/java/me/totalfreedom/totalfreedommod/command/CommandPermissions.java @@ -10,9 +10,12 @@ public @interface CommandPermissions Rank level() default Rank.NON_OP; + String permission() default ""; + SourceType source() default SourceType.BOTH; boolean blockHostConsole() default false; int cooldown() default 0; + } \ No newline at end of file diff --git a/src/main/java/me/totalfreedom/totalfreedommod/command/FreedomCommand.java b/src/main/java/me/totalfreedom/totalfreedommod/command/FreedomCommand.java index 017307a4..112b349c 100644 --- a/src/main/java/me/totalfreedom/totalfreedommod/command/FreedomCommand.java +++ b/src/main/java/me/totalfreedom/totalfreedommod/command/FreedomCommand.java @@ -43,6 +43,8 @@ public abstract class FreedomCommand implements CommandExecutor, TabCompleter private final String usage; private final String aliases; private final Rank level; + + private final String permission; private final SourceType source; private final boolean blockHostConsole; private final int cooldown; @@ -59,6 +61,7 @@ public abstract class FreedomCommand implements CommandExecutor, TabCompleter this.usage = params.usage(); this.aliases = params.aliases(); this.level = perms.level(); + this.permission = perms.permission().isEmpty() ? "totalfreedommod." + this.name.toLowerCase() : perms.permission(); this.source = perms.source(); this.blockHostConsole = perms.blockHostConsole(); this.cooldown = perms.cooldown(); @@ -162,8 +165,48 @@ public abstract class FreedomCommand implements CommandExecutor, TabCompleter } } + @Deprecated + protected void checkRank(@Deprecated Rank rank, String permission) + { + //TODO: Not bothering with BukkitTelnet now. + if (sender instanceof Player player) + { + if (!plugin.permissionHandler.hasPermission(player, permission)) + { + noPerms(); + return; + } + } + if (!plugin.rm.getRank(sender).isAtLeast(rank)) + { + noPerms(); + } + } + + protected void checkRank(String permission) + { + //TODO: Not bothering with BukkitTelnet now. + if (sender instanceof Player player) + { + if (!plugin.permissionHandler.hasPermission(player, permission)) + { + noPerms(); + } + } + } + + @Deprecated protected void checkRank(Rank rank) { + //TODO: Not bothering with BukkitTelnet now. + if (sender instanceof Player player) + { + if (!plugin.permissionHandler.hasPermission(player, this.permission)) + { + noPerms(); + return; + } + } if (!plugin.rm.getRank(sender).isAtLeast(rank)) { noPerms(); diff --git a/src/main/java/me/totalfreedom/totalfreedommod/config/ConfigEntry.java b/src/main/java/me/totalfreedom/totalfreedommod/config/ConfigEntry.java index abede2c7..c398a6d7 100644 --- a/src/main/java/me/totalfreedom/totalfreedommod/config/ConfigEntry.java +++ b/src/main/java/me/totalfreedom/totalfreedommod/config/ConfigEntry.java @@ -73,6 +73,9 @@ public enum ConfigEntry SERVER_WHITELIST_MOTD(String.class, "server.motds.whitelist"), SERVER_FULL_MOTD(String.class, "server.motds.full"), // + + PERMISSIONS_GROUPS_ADMIN(String.class, "permissions.groups.admin"), + PERMISSIONS_GROUPS_SENIOR(String.class, "permissions.groups.senior_admin"), DISCORD_TOKEN(String.class, "discord.token"), DISCORD_REPORT_CHANNEL_ID(String.class, "discord.report_channel_id"), DISCORD_CHAT_CHANNEL_ID(String.class, "discord.chat_channel_id"), diff --git a/src/main/java/me/totalfreedom/totalfreedommod/httpd/module/Module_list.java b/src/main/java/me/totalfreedom/totalfreedommod/httpd/module/Module_list.java index fa2f5f6d..2857f02f 100644 --- a/src/main/java/me/totalfreedom/totalfreedommod/httpd/module/Module_list.java +++ b/src/main/java/me/totalfreedom/totalfreedommod/httpd/module/Module_list.java @@ -144,4 +144,5 @@ public class Module_list extends HTTPDModule { return "TotalFreedom - Online Players"; } + } diff --git a/src/main/java/me/totalfreedom/totalfreedommod/permissions/handler/DefaultPermissionHandler.java b/src/main/java/me/totalfreedom/totalfreedommod/permissions/handler/DefaultPermissionHandler.java new file mode 100644 index 00000000..606828ff --- /dev/null +++ b/src/main/java/me/totalfreedom/totalfreedommod/permissions/handler/DefaultPermissionHandler.java @@ -0,0 +1,43 @@ +package me.totalfreedom.totalfreedommod.permissions.handler; + +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + + +/** + * @author Taah + * @project TotalFreedomMod + * @since 9:10 PM [23-05-2022] + */ +public class DefaultPermissionHandler implements IPermissionHandler +{ + @Override + public boolean hasPermission(@NotNull OfflinePlayer player, @Nullable String permission) + { + if (permission == null) + { + return true; + } + throw new UnsupportedOperationException("Unable to use Bukkit's native permission system for permissions!"); + } + + @Override + public boolean hasPermission(@NotNull Player player, @Nullable String permission) + { + return permission == null || player.hasPermission(permission); + } + + @Override + public boolean inGroup(@NotNull OfflinePlayer player, @Nullable String groupName) + { + throw new UnsupportedOperationException("Unable to use Bukkit's native permission system for groups!"); + } + + @Override + public boolean inGroup(@NotNull Player player, @Nullable String groupName) + { + throw new UnsupportedOperationException("Unable to use Bukkit's native permission system for groups!"); + } +} diff --git a/src/main/java/me/totalfreedom/totalfreedommod/permissions/handler/IPermissionHandler.java b/src/main/java/me/totalfreedom/totalfreedommod/permissions/handler/IPermissionHandler.java new file mode 100644 index 00000000..50192b37 --- /dev/null +++ b/src/main/java/me/totalfreedom/totalfreedommod/permissions/handler/IPermissionHandler.java @@ -0,0 +1,20 @@ +package me.totalfreedom.totalfreedommod.permissions.handler; + +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Taah + * @project TotalFreedomMod + * @since 9:05 PM [23-05-2022] + */ +public interface IPermissionHandler +{ + boolean hasPermission(@NotNull OfflinePlayer player, @Nullable String permission); + boolean hasPermission(@NotNull Player player, @Nullable String permission); + + boolean inGroup(@NotNull OfflinePlayer player, @Nullable String groupName); + boolean inGroup(@NotNull Player player, @Nullable String groupName); +} diff --git a/src/main/java/me/totalfreedom/totalfreedommod/permissions/handler/NMPermissionHandler.java b/src/main/java/me/totalfreedom/totalfreedommod/permissions/handler/NMPermissionHandler.java new file mode 100644 index 00000000..b6152068 --- /dev/null +++ b/src/main/java/me/totalfreedom/totalfreedommod/permissions/handler/NMPermissionHandler.java @@ -0,0 +1,95 @@ +package me.totalfreedom.totalfreedommod.permissions.handler; + +import me.totalfreedom.totalfreedommod.TotalFreedomMod; +import me.totalfreedom.totalfreedommod.util.FLog; +import net.milkbowl.vault.permission.Permission; +import nl.chimpgamer.networkmanager.api.NetworkManagerPlugin; +import nl.chimpgamer.networkmanager.api.NetworkManagerProvider; +import nl.chimpgamer.networkmanager.api.models.permissions.PermissionPlayer; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + + +/** + * @author Taah + * @project TotalFreedomMod + * @since 9:10 PM [23-05-2022] + */ +public class NMPermissionHandler implements IPermissionHandler +{ + private NetworkManagerPlugin plugin; + + public NMPermissionHandler(TotalFreedomMod plugin) + { + if (plugin.permissionHandler != null) + { + return; + } + if (!Bukkit.getPluginManager().isPluginEnabled("NetworkManager")) + { + plugin.permissionHandler = new DefaultPermissionHandler(); + return; + } + this.plugin = NetworkManagerProvider.get(); + } + + @Override + public boolean hasPermission(@NotNull OfflinePlayer player, @Nullable String permission) + { + if (permission == null) + { + return true; + } + PermissionPlayer permissionPlayer = this.plugin.getPermissionManager().getPermissionPlayer(player.getUniqueId()); + if (permissionPlayer == null) + { + FLog.debug("Unable to find permissions player in NetworkManager. Returning true."); + return true; + } + Boolean has = permissionPlayer.hasPermission(permission); + if (has == null) + { + FLog.debug("NetworkManager is idiotic and has a chance of returning null on a Boolean object. Returning true."); + return true; + } + return has; + } + + @Override + public boolean hasPermission(@NotNull Player player, @Nullable String permission) + { + return hasPermission((OfflinePlayer) player, permission); + } + + @Override + public boolean inGroup(@NotNull OfflinePlayer player, @Nullable String groupName) + { + if (groupName == null) + { + FLog.debug("NM Perms: Setting permission access to false, group is null"); + return false; + } + PermissionPlayer permissionPlayer = this.plugin.getPermissionManager().getPermissionPlayer(player.getUniqueId()); + if (permissionPlayer == null) + { + FLog.debug("NM Perms: Setting permission access to false, player not found in NM for '" + player.getUniqueId() + "'"); + return false; + } + if (permissionPlayer.getGroups().isEmpty()) + { + FLog.debug("NM Perms: Setting permission access to false, player groups are empty for '" + player.getUniqueId() + "'"); + return false; + } + return permissionPlayer.getGroups().stream().anyMatch(group -> group.getName().equals(groupName)); + } + + @Override + public boolean inGroup(@NotNull Player player, @Nullable String groupName) + { + return this.inGroup((OfflinePlayer) player, groupName); + } +} diff --git a/src/main/java/me/totalfreedom/totalfreedommod/permissions/handler/VaultPermissionHandler.java b/src/main/java/me/totalfreedom/totalfreedommod/permissions/handler/VaultPermissionHandler.java new file mode 100644 index 00000000..07ee9e4e --- /dev/null +++ b/src/main/java/me/totalfreedom/totalfreedommod/permissions/handler/VaultPermissionHandler.java @@ -0,0 +1,103 @@ +package me.totalfreedom.totalfreedommod.permissions.handler; + +import me.totalfreedom.totalfreedommod.TotalFreedomMod; +import me.totalfreedom.totalfreedommod.util.FLog; +import net.milkbowl.vault.permission.Permission; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + + +/** + * @author Taah + * @project TotalFreedomMod + * @since 9:10 PM [23-05-2022] + */ +public class VaultPermissionHandler implements IPermissionHandler +{ + private Permission permissions; + + public VaultPermissionHandler(TotalFreedomMod plugin) + { + if (plugin.permissionHandler != null) + { + return; + } + if (!Bukkit.getPluginManager().isPluginEnabled("Vault")) + { + plugin.permissionHandler = new DefaultPermissionHandler(); + return; + } + RegisteredServiceProvider rsp = Bukkit.getServicesManager().getRegistration(Permission.class); + if (rsp == null) + { + FLog.warning("Switching back to Bukkit's default permissions from Vault's due to no permission system found."); + plugin.permissionHandler = new DefaultPermissionHandler(); + return; + } + this.permissions = rsp.getProvider(); + plugin.permissionHandler = this; + } + + @Override + public boolean hasPermission(@NotNull OfflinePlayer player, @Nullable String permission) + { + if (this.permissions == null) + { + FLog.debug("Can't use Vault permissions system, there is no plugin using Vault permissions."); + return true; + } + return permission == null || this.permissions.playerHas(null, player, permission); + } + + @Override + public boolean hasPermission(@NotNull Player player, @Nullable String permission) + { + if (this.permissions == null) + { + FLog.debug("Can't use Vault permissions system, there is no plugin using Vault permissions."); + return true; + } + return permission == null || this.permissions.playerHas(player, permission); + } + + @Override + public boolean inGroup(@NotNull OfflinePlayer player, @Nullable String groupName) + { + if (this.permissions == null) + { + FLog.debug("Can't use Vault permissions system for groups, there is no plugin using Vault permissions."); + return true; + } + if (groupName == null) + { + FLog.debug("Vault Perms: Group name is null, returning false for group check"); + return false; + } + return this.permissions.playerInGroup(null, player, groupName); + } + + @Override + public boolean inGroup(@NotNull Player player, @Nullable String groupName) + { + if (this.permissions == null) + { + FLog.debug("Can't use Vault permissions system for groups, there is no plugin using Vault permissions."); + return false; + } + if (groupName == null) + { + FLog.debug("Vault Perms: Group name is null, returning false for group check"); + return false; + } + return this.permissions.playerInGroup(player, groupName); + } + + public Permission getPermissions() + { + return permissions; + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index b690154b..20a55f99 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -54,6 +54,12 @@ server: # What to display at the bottom of the tab list tablist_footer: '' +# Permissions System +permissions: + groups: + admin: "Administrator" + senior_admin: "Senior-Administrator" + # Discord discord: # If you do not have a token, make a bot account and get one at https://discordapp.com/developers/applications/me