public class UChatReloadEvent extends AbstractEvent implements Event {

    public UChatReloadEvent() {
    }

    @Override
    public Cause getCause() {
        return UChat.get().getVHelper().getCause(UChat.get().instance());
    }
} public class uChatAPI {

    public boolean registerNewTag(String tagName, String format, String clickCmd, List<String> hoverMessages, String permission, List<String> shoinworlds, List<String> hideinworlds, String clickUrl) {
        TagsCategory tagsCat = new TagsCategory(format, clickCmd, hoverMessages, permission, shoinworlds, hideinworlds, clickUrl);
        UChat.get().getConfig().root().tags.put(tagName, tagsCat);
        return true;
    }

    public boolean registerNewTag(String tagName, String format, String clickCmd, List<String> hoverMessages) {
        return registerNewTag(tagName, format, clickCmd, hoverMessages, null, null, null, null);
    }

    public boolean registerNewChannel(UCChannel channel) throws IOException {
        UChat.get().getConfig().addChannel(channel);
        UChat.get().getCmds().registerChannelAliases();
        UChat.get().reload();
        return true;
    }

    public boolean registerNewChannel(Map<String, Object> properties) throws IOException {
        UCChannel ch = new UCChannel(properties);
        return registerNewChannel(ch);
    }

    @Deprecated
    public boolean registerNewChannel(String chName, String chAlias, boolean crossWorlds, int distance, String color, String tagBuilder, boolean needFocus, boolean receiverMsg, double cost, String ddmode, String ddmcformat, String mcddformat, String ddhover, boolean ddallowcmds, boolean bungee) throws IOException {
        if (UChat.get().getChannel(chName) != null) {
            return false;
        }
        if (tagBuilder == null || tagBuilder.equals("")) {
            tagBuilder = UChat.get().getConfig().root().general.default_tag_builder;
        }
        UCChannel ch = new UCChannel(chName, chAlias, crossWorlds, distance, color, tagBuilder, needFocus, receiverMsg, cost, bungee, true, false, false, "player", "", new ArrayList<>(), "", ddmode, ddmcformat, mcddformat, ddhover, ddallowcmds, true);
        UChat.get().getConfig().addChannel(ch);
        UChat.get().reload();
        return true;
    }

    public UCChannel getChannel(String chName) {
        return UChat.get().getChannel(chName);
    }

    public UCChannel getPlayerChannel(Player player) {
        return UChat.get().getPlayerChannel(player);
    }

    public Collection<UCChannel> getChannels() {
        return UChat.get().getChannels().values();
    }

    /**
     * Filter your message strings by using uchat protections with this method.
     *
     * @param receiver The receiver
     * @param message  String message
     * @param channel  Receiver channel
     * @return Filtered message as string.
     */
    public String filterChatMessage(Player receiver, String message, UCChannel channel) {
        return UCChatProtection.filterChatMessage(receiver, message, channel);
    }

    /**
     * Get formated tag format from config with placeholders already parsed.
     *
     * @param tagname  Tag name from {@code tags} config section.
     * @param sender   The player to be the sender/owner of parsed tag.
     * @param receiver The player as receiver of tag. Use {@link Optional}.empty() to do not use a receiver.
     * @return Formatted tag or {@code null} if the tag is not on config.
     */
    public String getTagFormat(String tagname, Player sender, Optional<Player> receiver) {
        if (UChat.get().getConfig().root().tags.containsKey(tagname)) {
            String format = UChat.get().getConfig().root().tags.get(tagname).format;
            return UCMessages.formatTags(tagname, format, sender, receiver.isPresent() ? receiver.get() : "", "", UChat.get().getPlayerChannel(sender));
        }
        return null;
    }
} public class UChatBungee implements RawDataListener {
    private ChannelBinding.RawDataChannel chan;
    private UChatBungee listener;

    public UChatBungee(UChat plugin) {
        Sponge.getEventManager().registerListener(plugin.instance(), GameStartedServerEvent.class, (event) -> {
            this.chan = Sponge.getChannelRegistrar().createRawChannel(plugin.instance(), "bungee:uchat");
            this.listener = this;
            this.chan.addListener(Platform.Type.SERVER, this.listener);
        });
    }

    public void sendBungee(UCChannel ch, Text text) {
        this.chan.sendToAll((buf) -> {
            buf.writeUTF(UChat.get().getConfig().root().bungee.server_id);
            buf.writeUTF(ch.getAlias());
            buf.writeUTF(TextSerializers.JSON.serialize(text));
        });
    }

    @Override
    public void handlePayload(ChannelBuf data, RemoteConnection connection, Platform.Type side) {
        if (!side.equals(Platform.Type.SERVER)) {
            return;
        }

        String id = data.readUTF();
        String ch = data.readUTF();
        String json = data.readUTF();

        UCChannel chan = UChat.get().getChannel(ch);
        if (chan == null || !chan.isBungee()) {
            return;
        }

        if (chan.getDistance() == 0) {
            if (chan.neeFocus()) {
                for (String receiver : chan.getMembers()) {
                    if (Sponge.getServer().getPlayer(receiver).isPresent()) {
                        Sponge.getCommandManager().process(Sponge.getServer().getConsole(), "tellraw " + receiver + " " + json);
                    }
                }
            } else {
                for (Player receiver : Sponge.getServer().getOnlinePlayers()) {
                    if (UChat.get().getPerms().channelReadPerm(receiver, chan)) {
                        Sponge.getCommandManager().process(Sponge.getServer().getConsole(), "tellraw " + receiver.getName() + " " + json);
                    }
                }
            }
        }
        Sponge.getServer().getConsole().sendMessage(UCUtil.toText("&7Bungee message to channel " + chan.getName() + " from: " + id));
    }
} public class UCConfig {

    private final File defConfig = new File(UChat.get().configDir(), "config.conf");
    private final File defProt = new File(UChat.get().configDir(), "protections.conf");

    private CommentedConfigurationNode configRoot;
    private ConfigurationLoader<CommentedConfigurationNode> cfgLoader;
    private MainCategory root;
    private CommentedConfigurationNode protsRoot;
    private ConfigurationLoader<CommentedConfigurationNode> protLoader;
    private ProtectionsCategory protections;

    public UCConfig(GuiceObjectMapperFactory factory) throws IOException {
        UChat.get().getLogger().info("-> Config module");
        try {
            Files.createDirectories(UChat.get().configDir().toPath());
            if (!defConfig.exists()) {
                UChat.get().getLogger().info("Creating config file...");
                defConfig.createNewFile();
            }

            /*--------------------- config.conf ---------------------------*/
            String header = ""
                    + "Uchat configuration file\n"
                    + "Author: FabioZumbi12\n"
                    + "We recommend you to use NotePad++ to edit this file and avoid TAB errors!\n"
                    + "------------------------------------------------------------------------\n"
                    + "\n"
                    + "Tags is where you can customize what will show on chat, on hover or on click on tag.\n"
                    + "To add a tag, you can copy an existent and change the name and the texts.\n"
                    + "After add and customize your tag, put the tag name on 'general > default-tag-builder'.\n"
                    + "------------------------------------------------------------------------\n"
                    + "###### Do not rename the tags 'playername', 'nickname' and 'message' ########\n"
                    + "############ or the plugin will not parse the tag correctly! ################\n"
                    + "------------------------------------------------------------------------\n"
                    + "\n"
                    + "Available replacers:\n"
                    + " - {world}: Replaced by sender world;\n"
                    + " - {message}: Message sent by player;\n"
                    + " - {playername}: The name of player;\n"
                    + " - {nickname}: The nickname of player. If not set, will show realname;\n"
                    + " - {nick-symbol}: The symbol to use before nickname;\n"
                    + " - {ch-name}: Channel name;\n"
                    + " - {ch-alias}: Channel alias;\n"
                    + " - {ch-color}: Channel color;\n"
                    + " - {balance}: Get the sender money;\n"
                    + " - {hand-type}: Item type;\n"
                    + " - {hand-name}: Item name;\n"
                    + " - {hand-amount}: Item quantity;\n"
                    + " - {hand-lore}: Item description (lore);\n"
                    + " - {hand-durability}: Item durability;\n"
                    + " - {hand-enchants}: Item enchantments;\n"
                    + " - {time-now}: Prints the time now on server;\n"
                    + "\n"
                    + "Permissions Group Options:\n"
                    + " - {option_group}: Get the group name;\n"
                    + " - {option_prefix}: Get the prefix of group (if set);\n"
                    + " - {option_all_prefixes}: Get and show all prefixes the player has;\n"
                    + " - {option_suffix}: Get the suffix of group (if set);\n"
                    + " - {option_all_suffixes}:  Get and show all suffixes the player has;\n"
                    + " - {option_display_name}: Get the custom name of group (if set);\n"
                    + " - {option_<key option>}: Get some custom key option from your group in permissions like {option_home-count} to get home count from Nucleus;\n"
                    + "\n"
                    + "Permissions Player Options:\n"
                    + " - {player_option_prefix}: Get the prefix of player (if set);\n"
                    + " - {player_option_suffix}: Get the suffix of player (if set);\n"
                    + " - {player_option_<key option>}: Get some custom key option from your permissions like {player_option_viptime} or what you want;\n"
                    + "\n"
                    + "Vanilla Chat:\n"
                    + " - {chat_header}: Get the header of chat;\n"
                    + " - {chat_body}: Get the body of chat;\n"
                    + " - {chat_footer}: Get the footer of chat;\n"
                    + " - {chat_all}: Get all default formats;\n"
                    + "\n"
                    + "MCClans:\n"
                    + " - {clan_name}: The name of clan;\n"
                    + " - {clan_tag}: Clan tag;\n"
                    + " - {clan_tag_color}: Clan tag with colors;\n"
                    + " - {clan_kdr}: Clan KDR;\n"
                    + " - {clan_player_rank}: Get the player rank on Clan;\n"
                    + " - {clan_player_kdr}: Get the player KDR;\n"
                    + " - {clan_player_ffprotected}: Get if player is friendly fire protected;\n"
                    + " - {clan_player_isowner}: Get if this player os owner of this Clan;\n"
                    + "Jedis (Redis):\n"
                    + "- {jedis-id} - The ID of this server;\n"
                    + "\n";

            cfgLoader = HoconConfigurationLoader.builder().setFile(defConfig).build();
            configRoot = cfgLoader.load(ConfigurationOptions.defaults().setObjectMapperFactory(factory).setShouldCopyDefaults(true).setHeader(header));
            root = configRoot.getValue(TypeToken.of(MainCategory.class), new MainCategory());

            for (World w : Sponge.getServer().getWorlds()) {
                if (!root.general.default_channels.worlds.containsKey(w.getName())) {
                    root.general.default_channels.worlds.put(w.getName(), new MainCategory.WorldInfo(root.general.default_channels.default_channel, false));
                }
            }

            //update old configs
            double update = 0;
            if (configRoot.getNode("_config-version").getDouble() < 1.2D) {
                configRoot.getNode("_config-version").setValue(1.2D);

                configRoot.getNode("debug-messages").setValue(null);

                update = 1.2D;
            }

            if (update > 0) {
                UChat.get().getLogger().warning("Configuration updated to " + update);
            }

            /*--------------------- protections.conf ---------------------------*/
            protLoader = HoconConfigurationLoader.builder().setFile(defProt).build();
            protsRoot = protLoader.load(ConfigurationOptions.defaults().setObjectMapperFactory(factory).setShouldCopyDefaults(true));
            protections = protsRoot.getValue(TypeToken.of(ProtectionsCategory.class), new ProtectionsCategory());

        } catch (IOException | ObjectMappingException e) {
            e.printStackTrace();
        }

        /* Load Channels */
        loadChannels();

        //----------------------------------------------------------------------------------------//
        save();
        UChat.get().getLogger().info("All configurations loaded!");
    }

    public MainCategory root() {
        return this.root;
    }

    public Chatprotection protections() {
        return this.protections.chat_protection;
    }

    private void loadChannels() throws IOException {
        File chfolder = new File(UChat.get().configDir(), "channels");

        if (!chfolder.exists()) {
            chfolder.mkdir();
            UChat.get().getLogger().info("Created folder: " + chfolder.getPath());
        }

        //--------------------------------------- Load Aliases -----------------------------------//

        if (UChat.get().getChannels() == null) {
            UChat.get().setChannels(new HashMap<>());
        }

        File[] listOfFiles = chfolder.listFiles();

        CommentedConfigurationNode channel;
        ConfigurationLoader<CommentedConfigurationNode> channelManager;

        if (listOfFiles.length == 0) {
            //create default channels
            File g = new File(chfolder, "global.conf");
            channelManager = HoconConfigurationLoader.builder().setFile(g).build();
            channel = channelManager.load();
            channel.getNode("name").setValue("Global");
            channel.getNode("alias").setValue("g");
            channel.getNode("color").setValue("&2");
            channel.getNode("jedis").setValue(true);
            channelManager.save(channel);

            File l = new File(chfolder, "local.conf");
            channelManager = HoconConfigurationLoader.builder().setFile(l).build();
            channel = channelManager.load();
            channel.getNode("name").setValue("Local");
            channel.getNode("alias").setValue("l");
            channel.getNode("across-worlds").setValue(false);
            channel.getNode("distance").setValue(40);
            channel.getNode("color").setValue("&e");
            channelManager.save(channel);

            File ad = new File(chfolder, "admin.conf");
            channelManager = HoconConfigurationLoader.builder().setFile(ad).build();
            channel = channelManager.load();
            channel.getNode("name").setValue("Admin");
            channel.getNode("alias").setValue("ad");
            channel.getNode("color").setValue("&b");
            channel.getNode("jedis").setValue(true);
            channelManager.save(channel);

            listOfFiles = chfolder.listFiles();
        }

        for (File file : listOfFiles) {
            if (file.getName().endsWith(".conf")) {
                channelManager = HoconConfigurationLoader.builder().setFile(file).build();
                channel = channelManager.load();

                Map<String, Object> chProps = new HashMap<>();
                channel.getChildrenMap().forEach((key, value) -> {
                    if (value.hasMapChildren()) {
                        String rkey = key.toString();
                        for (Entry<Object, ? extends CommentedConfigurationNode> vl : value.getChildrenMap().entrySet()) {
                            chProps.put(rkey + "." + vl.getKey(), vl.getValue().getValue());
                        }
                    } else {
                        chProps.put(key.toString(), value.getValue());
                    }
                });

                UCChannel ch = new UCChannel(chProps);
                addChannel(ch);
            }
        }
    }

    public void delChannel(UCChannel ch) {
        UChat.get().getCmds().unregisterCmd(ch.getAlias());
        UChat.get().getCmds().unregisterCmd(ch.getName());
        for (Entry<List<String>, UCChannel> ch0 : UChat.get().getChannels().entrySet()) {
            if (ch0.getValue().equals(ch)) {
                UChat.get().getChannels().remove(ch0.getKey());
                break;
            }
        }
        File defch = new File(UChat.get().configDir(), "channels" + File.separator + ch.getName().toLowerCase() + ".conf");
        if (defch.exists()) {
            defch.delete();
        }
    }

    public void addChannel(UCChannel ch) throws IOException {
        CommentedConfigurationNode chFile;
        ConfigurationLoader<CommentedConfigurationNode> channelManager;
        File defch = new File(UChat.get().configDir(), "channels" + File.separator + ch.getName().toLowerCase() + ".conf");

        channelManager = HoconConfigurationLoader.builder().setFile(defch).build();
        chFile = channelManager.load();

        chFile.getNode("across-worlds").setComment(""
                + "###################################################\n"
                + "############## Channel Configuration ##############\n"
                + "###################################################\n"
                + "\n"
                + "This is the channel configuration.\n"
                + "You can change and copy this file to create as many channels you want.\n"
                + "This is the default options:\n"
                + "\n"
                + "name: Global - The name of channel.\n"
                + "alias: g - The alias to use the channel\n"
                + "across-worlds: true - Send messages of this channel to all worlds?\n"
                + "distance: 0 - If across worlds is false, distance to receive this messages.\n"
                + "color: &b - The color of channel\n"
                + "tag-builder: ch-tags,world,clan-tag,marry-tag,group-prefix,nickname,group-suffix,message - Tags of this channel\n"
                + "need-focus: false - Player can use the alias or need to use '/ch g' to use this channel?\n"
                + "canLock: true - Change if the player can use /<channel> to lock on channel.\n"
                + "receivers-message: true - Send chat messages like if no player near to receive the message?\n"
                + "cost: 0.0 - Cost to player use this channel.\n"
                + "use-this-builder: false - Use this tag builder or use the 'config.yml' tag-builder?\n"
                + "\n"
                + "channelAlias - Use this channel as a command alias.\n"
                + "  enable: true - Enable this execute a command alias?\n"
                + "  sendAs: player - Send the command alias as 'player' or 'console'?\n"
                + "  cmd: '' - Command to send on every message send by this channel.\n"
                + "available-worlds - Worlds and only this world where this chat can be used and messages sent/received.\n"
                + "discord:\n"
                + "  mode: NONE - The options are NONE, SEND, LISTEN, BOTH. If enabled and token code set and the channel ID matches with one discord channel, will react according the choosen mode.\n"
                + "  hover: &3Discord Channel: &a{dd-channel}\n"
                + "  format-to-mc: {ch-color}[{ch-alias}]&b{dd-rolecolor}[{dd-rolename}]{sender}&r: \n"
                + "  format-to-dd: :thought_balloon: **{sender}**: {message} \n"
                + "  allow-server-cmds: false - Use this channel to send commands from discord > minecraft.\n"
                + "  channelID: '' - The IDs of your Discord Channels. Enable debug on your discord to get the channel ID.\n"
                + "  Note: You can add more than one discord id, just separate by \",\" like: 13246579865498,3216587898754\n");

        ch.getProperties().forEach((key, value) -> chFile.getNode((Object[]) key.toString().split("\\.")).setValue(value));
        channelManager.save(chFile);

        if (UChat.get().getChannel(ch.getName()) != null) {
            ch.setMembers(UChat.get().getChannel(ch.getName()).getMembers());
            UChat.get().getChannels().remove(Arrays.asList(ch.getName().toLowerCase(), ch.getAlias().toLowerCase()));
        }
        UChat.get().getChannels().put(Arrays.asList(ch.getName().toLowerCase(), ch.getAlias().toLowerCase()), ch);
    }

    public String[] getDefBuilder() {
        return root.general.default_tag_builder.replace(" ", "").split(",");
    }

    public List<String> getChCmd() {
        return Arrays.asList(root.general.channel_cmd_aliases.replace(" ", "").split(","));
    }

    public List<String> getBroadcastAliases() {
        return Arrays.asList(root.broadcast.aliases.replace(" ", "").split(","));
    }

    public List<String> getTellAliases() {
        return Arrays.asList((root.tell.cmd_aliases + ",r").replace(" ", "").split(","));
    }

    public List<String> getMsgAliases() {
        return Arrays.asList(root.general.umsg_cmd_aliases.replace(" ", "").split(","));
    }

    public void save() {
        try {
            configRoot.setValue(TypeToken.of(MainCategory.class), root);
            cfgLoader.save(configRoot);

            protsRoot.setValue(TypeToken.of(ProtectionsCategory.class), protections);
            protLoader.save(protsRoot);
        } catch (IOException | ObjectMappingException e) {
            e.printStackTrace();
        }
    }
} public class UCLang {

    private static final HashMap<CommandSource, String> DelayedMessage = new HashMap<>();
    private final Properties loadedLang = new Properties() {
        @Override
        public synchronized Enumeration<Object> keys() {
            return Collections.enumeration(new TreeSet<>(super.keySet()));
        }
    };
    private final Properties baseLang = new Properties() {
        @Override
        public synchronized Enumeration<Object> keys() {
            return Collections.enumeration(new TreeSet<>(super.keySet()));
        }
    };
    private static String pathLang;

    public UCLang() {
        String resLang = "lang" + UChat.get().getConfig().root().language + ".properties";
        pathLang = UChat.get().configDir() + File.separator + resLang;

        File lang = new File(pathLang);
        if (!lang.exists()) {
            try {
                if (UChat.get().instance().getAsset(resLang).isPresent()) {
                    UChat.get().instance().getAsset(resLang).get().copyToDirectory(UChat.get().configDir().toPath());
                } else {
                    UChat.get().instance().getAsset("langEN-US.properties").get().copyToDirectory(UChat.get().configDir().toPath());
                    new File(UChat.get().configDir(),"langEN-US.properties" ).renameTo(lang);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            UChat.get().instance().getLogger().info("Created lang file: " + pathLang);
        }

        loadLang();
        loadbaseLang();
        updateLang();

        UChat.get().getLogger().info("Language file loaded - Using: " + UChat.get().getConfig().root().language);
    }

    private void loadbaseLang() {
        baseLang.clear();
        try {
            baseLang.load(UChat.get().instance().getAsset("langEN-US.properties").get().getUrl().openStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void loadLang() {
        loadedLang.clear();
        try {
            FileInputStream fileInput = new FileInputStream(pathLang);
            Reader reader = new InputStreamReader(fileInput, StandardCharsets.UTF_8);
            loadedLang.load(reader);
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (loadedLang.get("_lang.version") != null) {
            int langv = Integer.parseInt(((String) loadedLang.get("_lang.version")).replace(".", ""));
            int rpv = Integer.parseInt(UChat.get().instance().getVersion().get().replace(".", ""));
            if (langv < rpv || langv == 0) {
                UChat.get().instance().getLogger().info("Your lang file is outdated. Probably need strings updates!");
                UChat.get().instance().getLogger().info("Lang file version: " + loadedLang.get("_lang.version"));
                loadedLang.put("_lang.version", UChat.get().instance().getVersion().get());
            }
        }
    }

    private void updateLang() {
        for (Entry<Object, Object> linha : baseLang.entrySet()) {
            if (!loadedLang.containsKey(linha.getKey())) {
                loadedLang.put(linha.getKey(), linha.getValue());
            }
        }
        if (!loadedLang.containsKey("_lang.version")) {
            loadedLang.put("_lang.version", UChat.get().instance().getVersion().get());
        }
        try {
            loadedLang.store(new OutputStreamWriter(new FileOutputStream(pathLang), StandardCharsets.UTF_8), null);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public String get(CommandSource player, String key) {
        return UCMessages.formatTags("", get(key), player, "", "", UChat.get().getPlayerChannel(player));
    }

    public String get(String key) {
        String FMsg;

        if (loadedLang.get(key) == null) {
            FMsg = "&c&oMissing language string for &4" + key;
        } else {
            FMsg = loadedLang.get(key).toString();
        }
        return FMsg.replace("\\n", "\n");
    }

    public Text getText(String key, String additional) {
        return UCUtil.toText(get(key) + additional);
    }

    public Text getText(String key) {
        return UCUtil.toText(get(key));
    }

    public void sendMessage(final CommandSource p, String key) {
        if (DelayedMessage.containsKey(p) && DelayedMessage.get(p).equals(key)) {
            return;
        }

        if (loadedLang.get(key) == null) {
            p.sendMessage(UCUtil.toText(get(p, "_UChat.prefix") + UCMessages.formatTags("", key, p, "", "", UChat.get().getPlayerChannel(p))));
        } else if (get(key).equalsIgnoreCase("")) {
            return;
        } else {
            p.sendMessage(UCUtil.toText(get(p, "_UChat.prefix") + " " + get(p, key)));
        }

        DelayedMessage.put(p, key);
        Sponge.getScheduler().createSyncExecutor(UChat.get().instance()).schedule(() -> {
            DelayedMessage.remove(p);
        }, 1, TimeUnit.SECONDS);
    }

} public class VersionData {
    public static final String VERSION = "${project.version}";
}
 public interface UCDInterface {
    boolean JDAAvailable();

    void sendTellToDiscord(String text);

    void sendRawToDiscord(String text);

    void sendToDiscord(CommandSource sender, String text, UCChannel ch);

    void updateGame(String text);

    void sendCommandsToDiscord(String text);

    void sendPixelmonLegendary(String text);

    void shutdown();

    void setPlayerRole(String ddUser, List<String> ddRoleIds, String nick, List<String> configRoles);
} public class UCDiscord extends ListenerAdapter implements UCDInterface {
    private static final Map<String, ColorSet<Integer, Integer, Integer>> colorMap = new HashMap<>();

    static {
        colorMap.put("&0", new ColorSet<>(0, 0, 0));
        colorMap.put("&1", new ColorSet<>(0, 0, 170));
        colorMap.put("&2", new ColorSet<>(0, 170, 0));
        colorMap.put("&3", new ColorSet<>(0, 170, 170));
        colorMap.put("&4", new ColorSet<>(170, 0, 0));
        colorMap.put("&5", new ColorSet<>(170, 0, 170));
        colorMap.put("&6", new ColorSet<>(255, 170, 0));
        colorMap.put("&7", new ColorSet<>(170, 170, 170));
        colorMap.put("&8", new ColorSet<>(85, 85, 85));
        colorMap.put("&9", new ColorSet<>(85, 85, 255));
        colorMap.put("&a", new ColorSet<>(85, 255, 85));
        colorMap.put("&b", new ColorSet<>(85, 255, 255));
        colorMap.put("&c", new ColorSet<>(255, 85, 85));
        colorMap.put("&d", new ColorSet<>(255, 85, 255));
        colorMap.put("&e", new ColorSet<>(255, 255, 85));
        colorMap.put("&f", new ColorSet<>(255, 255, 255));
    }

    private JDA jda;
    private final UChat uchat;

    public UCDiscord(UChat plugin) {
        this.uchat = plugin;
        try {
            jda = JDABuilder.createDefault(this.uchat.getConfig().root().discord.token)
                    .addEventListeners(this)
                    .build().awaitReady();

            if (plugin.getConfig().root().discord.update_status) {
                String game = plugin.getLang().get("discord.game").replace("{online}", String.valueOf(Sponge.getServer().getOnlinePlayers().size()));
                Activity activity = Activity.playing(game);
                jda.getPresence().setActivity(activity);
            }
        } catch (IllegalArgumentException | InterruptedException e) {
            e.printStackTrace();
            return;
        } catch (LoginException e) {
            e.printStackTrace();
            uchat.getLogger().warning(e.getLocalizedMessage());
            uchat.getLogger().severe("The TOKEN is wrong or empty! Check you config and your token.");
            return;
        }

        if (UChat.get().getConfig().root().discord.update_status) {
            Sponge.getScheduler().createSyncExecutor(plugin).scheduleAtFixedRate(() ->
                    updateGame(UChat.get().getLang().get("discord.game").replace("{online}", String.valueOf(Sponge.getServer().getOnlinePlayers().stream().filter(p->!p.hasPermission("uchat.discord.hide")).count()))), 2, 2, TimeUnit.SECONDS);
        }
    }

    public void shutdown() {
        this.uchat.getLogger().info("Shutdown JDA...");
        this.jda.getRegisteredListeners().forEach(l -> this.jda.removeEventListener(l));
        this.jda.shutdown();
        this.uchat.getLogger().info("JDA disabled!");
    }

    /*   ------ color util --------   */
    private static String fromRGB(int r, int g, int b) {
        TreeMap<Integer, String> closest = new TreeMap<>();
        colorMap.forEach((color, set) -> {
            int red = Math.abs(r - set.getRed());
            int green = Math.abs(g - set.getGreen());
            int blue = Math.abs(b - set.getBlue());
            closest.put(red + green + blue, color);
        });
        return closest.firstEntry().getValue();
    }

    public boolean JDAAvailable() {
        return this.jda != null && this.jda.getStatus().isInit();
    }

    @Override
    public void onMessageReceived(MessageReceivedEvent e) {
        if (e.getAuthor().getId().equals(e.getJDA().getSelfUser().getId())) return;

        String message = e.getMessage().getContentRaw();
        String[] args = message.split(" ");

        //listen bot privates for dd sync
        if (e.getMessage().isFromType(ChannelType.PRIVATE)) {

            Guild gc = e.getJDA().getGuildById(this.uchat.getDDSync().getGuidId());
            try {
                if (gc.retrieveMemberById(e.getAuthor().getId()).complete(true).getRoles().stream().anyMatch(r -> this.uchat.getDDSync().isDDAdminRole(r.getId()))){
                    //listen for ;;help
                    if (args.length == 1 && args[0].equalsIgnoreCase(this.uchat.getDDSync().getDDHelpCommand())){
                        try {
                            e.getChannel().sendMessage(this.uchat.getLang().get("discord.sync-admin-help")).complete(true);
                        } catch (RateLimitedException e1) {
                            e1.printStackTrace();
                        }
                        return;
                    }


                    if (args[0].equalsIgnoreCase(this.uchat.getDDSync().getDDAdminCommand())){

                        //listen ;;admin list
                        if (args.length == 2 && args[1].equalsIgnoreCase("list")){
                            try {
                                e.getChannel().sendMessage(this.uchat.getLang().get("discord.sync-admin-list") + "\n" + this.uchat.getDDSync().getConnectedPlayers()).complete(true);
                            } catch (RateLimitedException e1) {
                                e1.printStackTrace();
                            }
                        }

                        //listen ;;admin remove <player>|<user id>
                        if (args.length == 3 && args[1].equalsIgnoreCase("remove")){
                            if (this.uchat.getDDSync().getSyncNickName(args[2]) != null || this.uchat.getDDSync().getPlayerDDId(args[2]) != null){
                                String nickName = this.uchat.getDDSync().getSyncNickName(args[2]);
                                if (nickName == null)
                                    nickName = this.uchat.getDDSync().getSyncNickName(this.uchat.getDDSync().getPlayerDDId(args[2]));
                                this.uchat.getDDSync().unlink(nickName);
                                try {
                                    e.getChannel().sendMessage(this.uchat.getLang().get("discord.sync-admin-removed").replace("{player}", nickName)).complete(true);
                                } catch (RateLimitedException e1) {
                                    e1.printStackTrace();
                                }
                            } else {
                                try {
                                    e.getChannel().sendMessage(this.uchat.getLang().get("discord.sync-admin-notremoved").replace("{player}", args[2])).complete(true);
                                } catch (RateLimitedException e1) {
                                    e1.printStackTrace();
                                }
                            }
                            return;
                        }

                        //listen ;;admin add player id
                        if (args.length == 4 && args[1].equalsIgnoreCase("add")){
                            if (this.uchat.getDDSync().getSyncNickName(args[3]) == null && this.uchat.getDDSync().getPlayerDDId(args[2]) == null){
                                try {
                                    this.uchat.getDDSync().setPlayerDDId(args[3], args[2], null);
                                    e.getChannel().sendMessage(this.uchat.getLang().get("discord.sync-admin-added")
                                            .replace("{player}", args[2])
                                            .replace("{id}", args[3]))
                                            .complete(true);
                                } catch (RateLimitedException e1) {
                                    e1.printStackTrace();
                                }
                            } else {
                                try {
                                    e.getChannel().sendMessage(this.uchat.getLang().get("discord.sync-admin-notadded")
                                            .replace("{player}", args[2]))
                                            .complete(true);
                                } catch (RateLimitedException e1) {
                                    e1.printStackTrace();
                                }
                            }
                            return;
                        }
                        return;
                    }
                } else if (args[0].equalsIgnoreCase(this.uchat.getDDSync().getDDHelpCommand()) || args[0].equalsIgnoreCase(this.uchat.getDDSync().getDDAdminCommand())){
                    try {
                        e.getChannel().sendMessage(this.uchat.getLang().get("discord.sync-noperm")).complete(true);
                    } catch (RateLimitedException e1) {
                        e1.printStackTrace();
                    }
                    return;
                }
            } catch (RateLimitedException rateLimitedException) {
                rateLimitedException.printStackTrace();
            }

            //listen ;;connect code
            if (args[0].equalsIgnoreCase(this.uchat.getDDSync().getDDCommand())){
                if (args.length == 2){
                    String code = args[1];
                    try {
                        if (this.uchat.getDDSync().getPendentCode(code) == null) {
                            e.getChannel().sendMessage(this.uchat.getLang().get("discord.sync-nopendent").replace("{code}", code)).complete(true);
                            return;
                        }

                        if (this.uchat.getDDSync().getSyncNickName(e.getAuthor().getId()) != null) {
                            e.getChannel().sendMessage(this.uchat.getLang().get("discord.sync-already")).complete(true);
                            return;
                        }

                        String player = this.uchat.getDDSync().getPendentCode(code);
                        this.uchat.getDDSync().setPlayerDDId(e.getAuthor().getId(), player, code);
                        e.getChannel().sendMessage(this.uchat.getLang().get("discord.sync-connectok")).complete(true);
                    } catch (RateLimitedException e1) {
                        e1.printStackTrace();
                    }
                } else {
                    try {
                        e.getChannel().sendMessage(this.uchat.getLang().get("discord.sync-usage").replace("{command}","`" + this.uchat.getDDSync().getDDCommand() + " <code>`")).complete(true);
                    } catch (RateLimitedException e1) {
                        e1.printStackTrace();
                    }
                }
            }
            return;
        }

        if (e.getMember() == null || e.getMember().getUser().isFake()) return;
        int used = 0;

        for (UCChannel ch : this.uchat.getChannels().values()) {
            if (ch.isListenDiscord() && ch.matchDiscordID(e.getChannel().getId())) {
                if (e.getMember().getUser().isBot() && !ch.AllowBot()) {
                    continue;
                }
                //check if is cmd
                if (message.startsWith(this.uchat.getConfig().root().discord.server_commands.alias) && ch.getDiscordAllowCmds()) {
                    message = message.replace(this.uchat.getConfig().root().discord.server_commands.alias + " ", "");
                    if (!this.uchat.getConfig().root().discord.server_commands.withelist.isEmpty()) {
                        int count = 0;
                        for (String cmd : this.uchat.getConfig().root().discord.server_commands.withelist) {
                            if (message.startsWith(cmd)) count++;
                        }
                        if (count == 0) continue;
                    }
                    if (!this.uchat.getConfig().root().discord.server_commands.blacklist.isEmpty()) {
                        int count = 0;
                        for (String cmd : this.uchat.getConfig().root().discord.server_commands.blacklist) {
                            if (message.startsWith(cmd)) count++;
                        }
                        if (count > 0) continue;
                    }
                    Sponge.getCommandManager().process(Sponge.getServer().getConsole(), message);
                    used++;
                } else {
                    Builder text = Text.builder();

                    //format prefixes tags
                    String formated = formatTags(ch.getDiscordtoMCFormat(), ch, e, "", "");

                    //add dd channel name to hover
                    String hovered = formatTags(ch.getDiscordHover(), ch, e, "", "");
                    text.append(Text.of(formated));
                    if (!hovered.isEmpty()) {
                        text.onHover(TextActions.showText(Text.of(hovered)));
                    }

                    //format message
                    if (!e.getMessage().getAttachments().isEmpty()) {
                        try {
                            text.onClick(TextActions.openUrl(new URL(e.getMessage().getAttachments().get(0).getUrl())));
                            text.onHover(TextActions.showText(Text.of(e.getMessage().getAttachments().get(0).getFileName())));
                            if (message.isEmpty()) {
                                text.append(Text.of("- Attachment -"));
                            } else {
                                text.append(Text.of(message));
                            }
                        } catch (MalformedURLException ignored) {
                        }
                    } else {
                        message = message.replace("\\n", "\n");

                        //emoji
                        Pattern pe = Pattern.compile(":(.+?):");
                        Matcher me = pe.matcher(message);
                        if (me.find())
                            message = message.replaceAll("<:(.+?)>", ":" + me.group(1) + ":");
                        else
                            message = message.replaceAll("<:(.+?)>", ":?:");

                        //user and role name
                        Pattern pp = Pattern.compile("<@(.+?)>");
                        Matcher mp = pp.matcher(message);
                        if (mp.find()){
                            if (e.getMessage().getMentionedRoles().stream().findFirst().isPresent()){
                                String role = e.getMessage().getMentionedRoles().stream().findFirst().get().getName();
                                message = message.replaceAll("<@(.+?)>", "@" + role);
                            } else
                            if (e.getMessage().getMentionedMembers().stream().findFirst().isPresent()){
                                String nick = e.getMessage().getMentionedMembers().stream().findFirst().get().getNickname();
                                if (nick == null || nick.isEmpty())
                                    nick = e.getMessage().getMentionedMembers().stream().findFirst().get().getEffectiveName();

                                message = message.replaceAll("<@(.+?)>", "@" + nick);
                            }
                            else
                                message = message.replaceAll("<@(.+?)>", "@?");
                        }

                        //channel
                        Pattern pc = Pattern.compile("<#(.+?)>");
                        Matcher mc = pc.matcher(message);
                        if (mc.find() && e.getMessage().getMentionedChannels().stream().anyMatch(chg -> chg.getId().equals(mc.group(1))))
                            message = message.replaceAll("<#(.+?)>", "#" + e.getMessage().getMentionedChannels().stream().filter(chg -> chg.getId().equals(mc.group(1))).findFirst().get().getName());
                        else
                            message = message.replaceAll("<#(.+?)>", "#?");

                        text.append(Text.of(message));
                    }
                    ch.sendMessage(Sponge.getServer().getConsole(), text.build(), true);
                    used++;
                }
            }
        }

        //check if is from log command chanel
        if (used == 0 && e.getChannel().getId().equals(UChat.get().getConfig().root().discord.commands_channel_id)) {
            if (!this.uchat.getConfig().root().discord.server_commands.withelist.isEmpty()) {
                int count = 0;
                for (String cmd : this.uchat.getConfig().root().discord.server_commands.withelist) {
                    if (message.startsWith(cmd)) count++;
                }
                if (count == 0) return;
            }
            if (!this.uchat.getConfig().root().discord.server_commands.blacklist.isEmpty()) {
                int count = 0;
                for (String cmd : this.uchat.getConfig().root().discord.server_commands.blacklist) {
                    if (message.startsWith(cmd)) count++;
                }
                if (count > 0) return;
            }
            Sponge.getCommandManager().process(Sponge.getServer().getConsole(), message);
        }
    }

    public void updateGame(String text) {
        String game = uchat.getLang().get("discord.game").replace("{online}", String.valueOf(Sponge.getServer().getOnlinePlayers().size()));
        Activity activity = Activity.playing(game);
        jda.getPresence().setActivity(activity);
    }

    public void sendTellToDiscord(String text) {
        if (!uchat.getConfig().root().discord.tell_channel_id.isEmpty()) {
            sendToChannel(uchat.getConfig().root().discord.tell_channel_id, text);
        }
    }

    public void sendCommandsToDiscord(String text) {
        if (!uchat.getConfig().root().discord.commands_channel_id.isEmpty()) {
            sendToChannel(uchat.getConfig().root().discord.commands_channel_id, text);
        }
    }

    public void sendRawToDiscord(String text) {
        if (!uchat.getConfig().root().discord.log_channel_id.isEmpty()) {
            sendToChannel(uchat.getConfig().root().discord.log_channel_id, text);
        }
    }

    public void sendPixelmonLegendary(String text) {
        if (!uchat.getConfig().root().discord.pixelmon.legendary_channel_id.isEmpty()) {
            sendToChannel(uchat.getConfig().root().discord.pixelmon.legendary_channel_id, text);
        }
    }

    public void sendToDiscord(CommandSource sender, String text, UCChannel ch) {
        if (ch.isSendingDiscord()) {
            if (!uchat.getPerms().hasPerm(sender, "discord.mention")) {
                text = text.replace("@everyone", "everyone")
                        .replace("@here", "here");
            }
            text = formatTags(ch.getMCtoDiscordFormat(), ch, null, sender.getName(), text);
            for (String ddid : ch.getDiscordChannelID()) {
                sendToChannel(ddid, text);
            }
        }
    }

    private void sendToChannel(String id, String text) {
        text = text.replaceAll("([&§]([a-fk-or0-9]))", "");
        TextChannel ch = jda.getTextChannelById(id);
        try {
            ch.sendMessage(text).queue();
        } catch (PermissionException e) {
            uchat.getLogger().severe("JDA: No permission to send messages to channel " + ch.getName() + ".");
        } catch (Exception e) {
            uchat.getLogger().warning("JDA: The channel ID is incorrect, not available or Discord is offline, in maintenance or some other connection problem.");
            e.printStackTrace();
        }
    }

    public void setPlayerRole(String ddUser, List<String> ddRoleIds, String nick, List<String> configRoles) {
        try {
            Guild gc = this.jda.getGuildById(this.uchat.getDDSync().getGuidId());
            Member member;
            try {
                member = gc.retrieveMemberById(ddUser).complete(true);
            } catch (RateLimitedException ignored) {
                member = gc.getMemberById(ddUser);
            }
            if (!nick.isEmpty() && (member.getNickname() == null || !member.getNickname().equals(nick))) {
                gc.retrieveMember(member.getUser()).complete(true).modifyNickname(nick).complete(true);
            }

            List<Role> roles = new ArrayList<>();
            ddRoleIds.forEach(r -> roles.add(gc.getRoleById(r)));
            gc.modifyMemberRoles(member, roles, configRoles.stream().map(r->gc.getRoleById(r)).filter(r-> r != null && !roles.contains(r)).collect(Collectors.toList())).complete(true);
        } catch (ErrorResponseException e) {
            UChat.get().getLogger().warning("Jda response error: " + e.getLocalizedMessage());
            UChat.get().getLogger().warning("Additional info: User ID:" + ddUser);
            if (this.uchat.getDDSync().getSyncNickName(ddUser) != null)
                this.uchat.getDDSync().unlink(this.uchat.getDDSync().getSyncNickName(ddUser));
        } catch (RateLimitedException e) {
            UChat.get().getLogger().warning("Jda Rate Limited Exception: " + e.getLocalizedMessage());
            e.printStackTrace();
        }
    }

    private String formatTags(String format, UCChannel ch, MessageReceivedEvent e, String sender, String message) {
        format = format.replace("{ch-color}", ch.getColor())
                .replace("{ch-alias}", ch.getAlias())
                .replace("{ch-name}", ch.getName());
        if (e != null) {
            sender = UCUtil.stripColor('§', UCUtil.toColor(e.getMember().getEffectiveName()));
            format = format.replace("{sender}", sender)
                    .replace("{dd-channel}", e.getChannel().getName())
                    .replace("{message}", e.getMessage().getContentRaw());
            if (!e.getMember().getRoles().isEmpty()) {
                Role role = e.getMember().getRoles().get(0);
                if (role.getColor() != null) {
                    format = format.replace("{dd-rolecolor}", fromRGB(
                            role.getColor().getRed(),
                            role.getColor().getGreen(),
                            role.getColor().getBlue()));
                }
                format = format.replace("{dd-rolename}", role.getName());
            }
            if (e.getMember().getNickname() != null) {
                format = format.replace("{nickname}", UCUtil.stripColor('§', UCUtil.toColor(e.getMember().getNickname())));
            } else {
                format = format.replace("{nickname}", sender);
            }
        }
        //if not filtered
        format = format
                .replace("{sender}", sender)
                .replace("{message}", message);
        format = format.replaceAll("\\{.*\\}", "");
        return UCUtil.toColor(format);
    }

    private static class ColorSet<R, G, B> {
        R red;
        G green;
        B blue;

        ColorSet(R red, G green, B blue) {
            this.red = red;
            this.green = green;
            this.blue = blue;
        }

        private R getRed() {
            return red;
        }

        private G getGreen() {
            return green;
        }

        private B getBlue() {
            return blue;
        }
    }
} public class UCDiscordSync {
    private CommentedConfigurationNode configRoot;
    private ConfigurationLoader<CommentedConfigurationNode> cfgLoader;
    private DiscordSync sync;
    private UUID taskId = null;
    private final File syncFile = new File(UChat.get().configDir(), "discord-sync.conf");

    public UCDiscordSync(ObjectMapperFactory factory) {
        CommandManager manager = Sponge.getCommandManager();
        try {
            if (!syncFile.exists()) {
                syncFile.createNewFile();
            }

            String header = ""
                    + "Uchat Discord Syncronization file\n"
                    + "Author: FabioZumbi12\n"
                    + "We recommend you to use NotePad++ to edit this file and avoid TAB errors!\n"
                    + "------------------------------------------------------------------------\n"
                    + "    In this file you can configure the discord roles synchronization    \n"
                    + "             between online players and your Discord groups             \n"
                    + "------------------------------------------------------------------------\n"
                    + "\n"
                    + "IMPORTANT NOTE: The BOT need to have a role assigned with MANAGE ROLES and MANAGE NICKNAMES\n"
                    + "granted, and this role need to be UNDER other roles you want to give to your Discord members!\n"
                    + "Check our WIKI to see how to setup Synchronization: https://bit.ly/2F3UoRf"
                    + "\n";

            cfgLoader = HoconConfigurationLoader.builder().setFile(syncFile).build();
            configRoot = cfgLoader.load(ConfigurationOptions.defaults().setObjectMapperFactory(factory).setShouldCopyDefaults(true).setHeader(header));
            sync = configRoot.getValue(TypeToken.of(DiscordSync.class), new DiscordSync());
        } catch (Exception ignored){}

        if (manager.get("discord-sync").isPresent()) {
            manager.removeMapping(manager.get("discord-sync").get());
        }

        if (this.sync.enable_sync) {
            CommandSpec listCmd = CommandSpec.builder()
                    .description(Text.of("Command to list all group ids"))
                    .permission("uchat.discord-sync.cmd.addgroup")
                    .executor((src, args) -> {
                        StringBuilder list = new StringBuilder();
                        for (Map.Entry<String, List<String>> key : this.sync.group_ids.entrySet()) {
                            list.append("&3- ").append(key.getKey()).append(": &b").append(key.getValue()).append("\n");
                        }
                        UChat.get().getLang().sendMessage(src,  UChat.get().getLang().get("discord.sync-group-list") + "\n" + list.toString());
                        return CommandResult.success();
                            })
                    .build();

            CommandSpec addGroupCmd = CommandSpec.builder()
                    .arguments(GenericArguments.string(Text.of("group")), GenericArguments.string(Text.of("id")))
                    .description(Text.of("Command to add in-game groups"))
                    .permission("uchat.discord-sync.cmd.addgroup")
                    .executor((src, args) -> {
                        String group = args.<String>getOne("group").get();
                        String id = args.<String>getOne("id").get();
                        List<String> groups = this.sync.group_ids.getOrDefault(group, new ArrayList<>());
                        if (!groups.contains(id)) {
                            groups.add(id);
                        }
                        this.sync.group_ids.put(group, groups);
                        saveConfig();
                        UChat.get().getLang().sendMessage(src,  UChat.get().getLang().get("discord.sync-groupadded")
                                .replace("{group}", group)
                                .replace("{id}", id));
                        return CommandResult.success();
                    })
                    .build();

            CommandSpec remGroupCmd = CommandSpec.builder()
                    .arguments(GenericArguments.string(Text.of("group")))
                    .description(Text.of("Command to remove in-game groups"))
                    .permission("uchat.discord-sync.cmd.addgroup")
                    .executor((src, args) -> {
                        String group = args.<String>getOne("group").get();
                        this.sync.group_ids.remove(group);
                        saveConfig();
                        UChat.get().getLang().sendMessage(src,  UChat.get().getLang().get("discord.sync-groupremoved")
                                .replace("{group}", group));
                        return CommandResult.success();
                    })
                    .build();

            CommandSpec generateCmd = CommandSpec.builder()
                    .arguments(GenericArguments.optional(GenericArguments.string(Text.of("player"))))
                    .description(Text.of("Command to generate a new key to sync an in-game nick to discord id"))
                    .permission("uchat.discord-sync.cmd.generate")
                    .executor((src, args) -> {
                        if (!args.hasAny("player") && src instanceof Player){
                            Player p = (Player) src;
                            String code = getSaltString();
                            if (getPlayerPending(p.getName()) != null) {
                                code = getPlayerPending(p.getName());
                            }
                            if (addPendingCode(p.getName(), code)) {
                                Text fancy = Text.builder().append(UCUtil.toText(UChat.get().getLang().get("_UChat.prefix") + " " + UChat.get().getLang().get("discord.sync-done").replace("{code}", code)))
                                        .onClick(TextActions.suggestCommand(code))
                                        .onHover(TextActions.showText(UCUtil.toText(UChat.get().getLang().get("discord.sync-click"))))
                                        .build();
                                p.sendMessage(fancy);
                            } else {
                                Text fancy = Text.builder().append(UCUtil.toText(UChat.get().getLang().get("_UChat.prefix") + " " + UChat.get().getLang().get("discord.sync-fail"))).build();
                                p.sendMessage(fancy);
                            }
                            return CommandResult.success();
                        }
                        if (args.hasAny("player") && src.hasPermission("uchat.discord-sync.cmd.generate.others")){
                            String p = args.<String>getOne("player").get();
                            String code = getSaltString();
                            if (getPlayerPending(p) != null) {
                                code = getPlayerPending(p);
                            }
                            if (addPendingCode(p, code)) {
                                Text fancy = Text.builder().append(UCUtil.toText(UChat.get().getLang().get("_UChat.prefix") + " " + UChat.get().getLang().get("discord.sync-done").replace("{code}", code)))
                                        .onClick(TextActions.suggestCommand(code))
                                        .onHover(TextActions.showText(UCUtil.toText(UChat.get().getLang().get("discord.sync-click"))))
                                        .build();
                                src.sendMessage(fancy);
                            } else {
                                Text fancy = Text.builder().append(UCUtil.toText(UChat.get().getLang().get("_UChat.prefix") + " " + UChat.get().getLang().get("discord.sync-fail"))).build();
                                src.sendMessage(fancy);
                            }
                            return CommandResult.success();
                        }
                        throw new CommandException(Text.of(), true);
                    })
                    .build();

            CommandSpec unlinkCmd = CommandSpec.builder()
                    .arguments(GenericArguments.optional(GenericArguments.string(Text.of("player"))))
                    .description(Text.of("Command to disconnect form discord"))
                    .permission("uchat.discord-sync.cmd.unlink")
                    .executor((src, args) -> {
                        if (!args.hasAny("player") && src instanceof Player){
                            Player p = (Player) src;
                            if (getPlayerDDId(p.getName()) != null) {
                                unlink(p.getName());
                                UChat.get().getLang().sendMessage(p, "discord.sync-unlink");
                            } else {
                                UChat.get().getLang().sendMessage(p, "discord.sync-notlink");
                            }
                            return CommandResult.success();
                        }
                        if (args.hasAny("player") && src.hasPermission("uchat.discord-sync.cmd.unlink.others")){
                            String p = args.<String>getOne("player").get();
                            if (getPlayerDDId(p) != null) {
                                unlink(p);
                                UChat.get().getLang().sendMessage(src, "discord.sync-unlink");
                            } else {
                                UChat.get().getLang().sendMessage(src, "discord.sync-notlink");
                            }
                            return CommandResult.success();
                        }
                        throw new CommandException(Text.of(), true);
                    })
                    .build();

            manager.register(UChat.get().instance(), CommandSpec.builder()
                    .permission("uchat.discord-sync.cmd.base")
                    .description(Text.of("Commando to manage uchat Discord Synchronization"))
                    .executor((src, args) -> {
                        src.sendMessage(UCUtil.toText("&b---------------- " + UChat.get().instance().getName() + " " + UChat.get().instance().getVersion().get() + " ----------------"));
                        src.sendMessage(UCUtil.toText("&bDeveloped by &6" + UChat.get().instance().getAuthors().get(0) + "."));
                        src.sendMessage(UCUtil.toText("&bDiscord Sync Commands [&6/dd-sync gen|unlink&b]."));
                        src.sendMessage(UCUtil.toText("&b-----------------------------------------------------"));
                        return CommandResult.success();
                    })
                    .child(listCmd, "list")
                    .child(addGroupCmd, "addgroup")
                    .child(remGroupCmd, "removegroup")
                    .child(generateCmd, "gen")
                    .child(unlinkCmd, "unlink")
                    .build(), "discord-sync", "dd-sync");

            final int interval = this.sync.update_interval;


            taskId = Sponge.getScheduler().createAsyncExecutor(UChat.get()).scheduleAtFixedRate(() -> {
                if (this.sync.group_ids.isEmpty())
                    return;

                final int[] delay = {0};
                Sponge.getServer().getOnlinePlayers().forEach(p -> {
                    if (getPlayerDDId(p.getName()) != null) {
                        String pId = getPlayerDDId(p.getName());
                        Sponge.getScheduler().createAsyncExecutor(UChat.get()).schedule(() -> {

                            String nick = "";
                            if (this.sync.name.to_discord) {
                                if (this.sync.name.use_display_name)
                                    nick = p.getDisplayNameData().displayName().get().toPlain();
                                else
                                    nick = p.getName();
                            }
                            String group = null;
                            try {
                                group = UChat.get().getPerms().getGroupAndTag(p).getIdentifier();
                            } catch (InterruptedException | ExecutionException e) {
                                e.printStackTrace();
                            }

                            List<String> roles = getDDRoleByInGameGroup(group);
                            if (roles != null && !roles.isEmpty()) {
                                if (this.sync.require_perms)
                                    roles.removeIf(r -> !p.hasPermission("uchat.discord-sync.role." + r));

                                UChat.get().getUCJDA().setPlayerRole(pId, roles, nick, getConfigRoles());
                            }

                            delay[0] += 500;
                        }, delay[0], TimeUnit.MILLISECONDS);
                    }
                });
            }, 1, Math.max(interval, 1), TimeUnit.SECONDS).getTask().getUniqueId();

            UChat.get().getLogger().info("- Discord Sync in use!");
        }
        saveConfig();
    }

    public void unload() {
        try {
            if (this.taskId != null && Sponge.getScheduler().getTaskById(this.taskId).isPresent())
                Sponge.getScheduler().getTaskById(this.taskId).get().cancel();
        } catch (Exception ignored) {}
    }

    String getConnectedPlayers(){
        StringBuilder list = new StringBuilder();
        try {
            for (Map.Entry<String, String> key : this.sync.sync_database.sync_players.entrySet()) {
                list.append(key.getKey()).append(": `").append(key.getValue()).append("`\n");
            }
        } catch (Exception ignored){}
        return list.toString();
    }

    @Nullable String getGuidId(){
        return this.sync.guild_id;
    }

    boolean isDDAdminRole(String role) {
        return this.sync.discord_cmds.admin_roles.contains(role);
    }

    String getDDAdminCommand() {
        return this.sync.discord_cmds.admin;
    }

    String getDDCommand() {
        return this.sync.discord_cmds.connect;
    }

    String getDDHelpCommand() {
        return this.sync.discord_cmds.help;
    }

    private boolean addPendingCode(String player, String code) {
        if (getPlayerDDId(player) != null) {
            return false;
        }
        this.sync.sync_database.pending_codes.put(code, player);
        saveConfig();
        return true;
    }

    @Nullable
    private String getPlayerPending(String player) {
        try {
            for (Map.Entry<String, String> key : this.sync.sync_database.pending_codes.entrySet()) {
                if (key.getValue().equals(player)) return key.getKey();
            }
        } catch (Exception ignored) {
        }
        return null;
    }

    String getPendentCode(String code) {
        return this.sync.sync_database.pending_codes.get(code);
    }

    void setPlayerDDId(String ddId, String nickName,@Nullable String code) {
        if (code != null) this.sync.sync_database.pending_codes.remove(code);
        this.sync.sync_database.sync_players.put(nickName, ddId);
        saveConfig();
    }

    @Nullable String getSyncNickName(String ddId) {
        try {
            for (Map.Entry<String, String> key : this.sync.sync_database.sync_players.entrySet()) {
                if (key.getValue().equals(ddId)) return key.getKey();
            }
        } catch (Exception ignored) {
        }
        return null;
    }

    @Nullable
    String getPlayerDDId(String player) {
        return this.sync.sync_database.sync_players.get(player);
    }

    private List<String> getDDRoleByInGameGroup(String group) {
        return this.sync.group_ids.get(group);
    }

    private List<String> getConfigRoles() {
        List<String> roles = new ArrayList<>();
        this.sync.group_ids.forEach((key, value) -> roles.addAll(value));
        return roles;
    }

    void unlink(String player){
        this.sync.sync_database.sync_players.remove(player);
        saveConfig();
    }

    private String getSaltString() {
        String SALTCHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
        StringBuilder salt = new StringBuilder();
        Random rnd = new Random();
        while (salt.length() < 10) { // length of the random string.
            int index = (int) (rnd.nextFloat() * SALTCHARS.length());
            salt.append(SALTCHARS.charAt(index));
        }
        return salt.toString();
    }

    private void saveConfig() {
        try {
            configRoot.setValue(TypeToken.of(DiscordSync.class), sync);
            cfgLoader.save(configRoot);
        } catch (IOException | ObjectMappingException e) {
            e.printStackTrace();
        }
    }
} public class UCJedisLoader {
    protected HashMap<String, String> tellPlayers = new HashMap<>();
    private JedisPool pool;
    private final String[] channels;
    private final ChatChannel channel;
    private final String thisId;
    private final String ip;
    private final int port;
    private final String auth;
    private final JedisPoolConfig poolCfg;
    private PubSubListener psl;

    public UCJedisLoader(String ip, int port, String auth, List<UCChannel> channels) {
        this.thisId = UChat.get().getConfig().root().jedis.server_id.replace("$", "");
        this.ip = ip;
        this.port = port;
        this.auth = auth;

        channels.add(new UCChannel("generic"));
        channels.add(new UCChannel("tellsend"));
        channels.add(new UCChannel("tellresponse"));

        String[] newChannels = new String[channels.size()];
        for (int i = 0; i < channels.size(); i++) {
            newChannels[i] = channels.get(i).getName().toLowerCase();
        }

        this.channels = newChannels;
        channel = new ChatChannel(newChannels);
        poolCfg = new JedisPoolConfig();


        //connect
        if (connectPool()) {

            psl = new PubSubListener();
            new Thread(psl, "UltimateChat PubSub Listener").start();

            UChat.get().getLogger().info("REDIS conected.");
        }
    }

    protected JedisPool getPool() {
        return this.pool;
    }
    private Task reconChecker;

    private boolean connectPool() {
        if (this.pool == null || this.pool.isClosed()) {
            if (auth.isEmpty()) {
                this.pool = new JedisPool(poolCfg, ip, port, 0);
            } else {
                this.pool = new JedisPool(poolCfg, ip, port, 0, auth);
            }

            reconChecker = Sponge.getScheduler().createAsyncExecutor(UChat.get()).scheduleAtFixedRate(() -> {
                Jedis jedis;
                try{
                    jedis = pool.getResource();
                    jedis.ping();
                } catch (JedisConnectionException e) {
                    UChat.get().getLogger().info("REDIS connection lost, trying to reconnect...");
                    if (psl != null) psl.poison();
                    if (pool != null) pool.destroy();
                    if (auth.isEmpty()) {
                        this.pool = new JedisPool(poolCfg, ip, port, 0);
                    } else {
                        this.pool = new JedisPool(poolCfg, ip, port, 0, auth);
                    }
                }
            }, 10, 10, TimeUnit.MINUTES).getTask();
            return false;
        }
        return true;
    }

    public void sendTellMessage(CommandSource sender, String tellReceiver, Text msg) {
        Builder text = Text.builder();
        text.append(UCUtil.toText(this.thisId));

        //send spy
        if (!sender.hasPermission("uchat.chat-spy.bypass")) {
            for (Player receiver : Sponge.getServer().getOnlinePlayers()) {
                if (!receiver.getName().equals(tellReceiver) && !receiver.equals(sender) &&
                        UChat.get().isSpy.contains(receiver.getName()) && UChat.get().getPerms().hasSpyPerm(receiver, "private")) {
                    String spyformat = UChat.get().getConfig().root().general.spy_format;

                    spyformat = spyformat.replace("{output}", UCUtil.stripColor('&', UCMessages.sendMessage(sender, tellReceiver, msg, new UCChannel("tell")).toPlain()));
                    receiver.sendMessage(UCUtil.toText(spyformat));
                }
            }
        }

        text.append(UCMessages.sendMessage(sender, tellReceiver, msg, new UCChannel("tell")));
        tellPlayers.put(tellReceiver, sender.getName());

        if (Arrays.asList(channels).contains("tellsend")) {
            Sponge.getScheduler().createAsyncExecutor(UChat.get()).execute(() -> {
                Jedis jedis = pool.getResource();
                try {
                    //string 0 1 2
                    jedis.publish("tellsend", this.thisId + "$" + tellReceiver + "$" + TextSerializers.JSON.serialize(text.build()));
                } catch (JedisConnectionException e) {
                    e.printStackTrace();
                }
            });
        }
    }

    public void sendRawMessage(Text value) {

        if (Arrays.asList(channels).contains("generic")) {
            Sponge.getScheduler().createAsyncExecutor(UChat.get()).execute(() -> {
                Jedis jedis = pool.getResource();
                try {
                    //string 0 1
                    jedis.publish("generic", this.thisId + "$" + TextSerializers.JSON.serialize(value));
                } catch (JedisConnectionException e) {
                    e.printStackTrace();
                }
            });
        }
    }

    public void sendMessage(String channel, Text value) {

        if (Arrays.asList(channels).contains(channel)) {
            Sponge.getScheduler().createAsyncExecutor(UChat.get().instance()).execute(() -> {
                //connectPool();
                Jedis jedis = pool.getResource();
                try {
                    //string 0 1
                    jedis.publish(channel, this.thisId + "$" + TextSerializers.JSON.serialize(value));
                } catch (JedisConnectionException e) {
                    e.printStackTrace();
                }
            });
        }
    }

    public void closePool() {
        UChat.get().getLogger().info("Closing REDIS...");
        if (psl != null) psl.poison();
        if (pool != null) pool.destroy();
        if (reconChecker != null) reconChecker.cancel();
        UChat.get().getLogger().info("REDIS closed.");
    }

    private class PubSubListener implements Runnable {

        @Override
        public void run() {
            try {
                Jedis rsc = pool.getResource();
                rsc.subscribe(channel, channels);
            } catch (JedisException | ClassCastException ignored) {
            }
        }

        private void poison() {
            try {
                channel.unsubscribe();
            } catch (Exception ignored) {
            }
        }
    }
} public class UCLogger {

    private final ConsoleSource console;
    private long start = 0;

    public UCLogger(Server serv) {
        this.console = serv.getConsole();
    }

    public void logClear(String s) {
        console.sendMessage(UCUtil.toText(s));
    }

    public void sucess(String s) {
        console.sendMessage(UCUtil.toText("UltimateChat: [&a&l" + s + "&r]"));
    }

    public void info(String s) {
        console.sendMessage(UCUtil.toText("UltimateChat: [" + s + "&r]"));
    }

    public void warning(String s) {
        console.sendMessage(UCUtil.toText("UltimateChat: [&6" + s + "&r]"));
    }

    public void severe(String s) {
        console.sendMessage(UCUtil.toText("UltimateChat: [&c&l" + s + "&r]"));
    }

    public void log(String s) {
        console.sendMessage(UCUtil.toText("UltimateChat: [" + s + "&r]"));
    }

    public void debug(String s) {
        if (UChat.get().getConfig() != null && UChat.get().getConfig().root().debug.messages) {
            console.sendMessage(UCUtil.toText("UltimateChat: [&b" + s + "&r]"));
        }
    }

    public void timings(timingType type, String message) {
        if (UChat.get().getConfig() != null && UChat.get().getConfig().root().debug.timings) {
            switch (type) {
                case START:
                    long diff = 0;
                    if (System.currentTimeMillis() - start > 5000) start = 0;
                    if (start != 0) {
                        diff = System.currentTimeMillis() - start;
                    }
                    start = System.currentTimeMillis();
                    console.sendMessage(UCUtil.toText("&3UC Timings - " + type + ": " + diff + "ms (" + message + "&3)&r"));
                    break;
                case END:
                    console.sendMessage(UCUtil.toText("&3UC Timings - " + type + ": " + (System.currentTimeMillis() - start) + "ms (" + message + "&3)&r"));
                    break;
                default:
                    break;
            }
        }
    }

    public enum timingType {
        START, END
    }
} public interface UCPerms {
    boolean hasSpyPerm(CommandSource receiver, String ch);

    boolean cmdPerm(CommandSource p, String cmd);

    boolean channelReadPerm(CommandSource p, UCChannel ch);

    boolean channelWritePerm(CommandSource p, UCChannel ch);

    boolean hasPerm(CommandSource p, String perm);

    boolean canIgnore(CommandSource sender, Object toignore);

    Subject getGroupAndTag(User player) throws InterruptedException, ExecutionException;

    HashMap<Integer, Subject> getPlayerGroups(User player) throws ExecutionException, InterruptedException;
} public class UCPlaceHoldersRelational {

    public UCPlaceHoldersRelational(UChat plugin) {
        PlaceholderService service = Sponge.getServiceManager().provideUnchecked(PlaceholderService.class);

        service.loadAll(this, plugin).forEach(builder -> {
            if (builder.getId().startsWith("uchat-")) {
                builder.author("FabioZumbi12");
                builder.version(plugin.instance().getVersion().get());
                try {
                    builder.buildAndRegister();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        });
    }

    @Placeholder(id = "uchat-channelname")
    public String channelName(@Source CommandSource p) {
        return UChat.get().getPlayerChannel(p).getName();
    }

    @Placeholder(id = "uchat-channelalias")
    public String channelAlias(@Source CommandSource p) {
        return UChat.get().getPlayerChannel(p).getAlias();
    }

    @Placeholder(id = "uchat-channelcolor")
    public String channelColor(@Source CommandSource p) {
        return UChat.get().getPlayerChannel(p).getColor();
    }

    @Placeholder(id = "uchat-tellwith")
    public String tellWith(@Source CommandSource p) {
        if (UChat.get().tellPlayers.containsKey(p.getName())) {
            return UChat.get().tellPlayers.get(p.getName());
        }
        return "--";
    }

    @Placeholder(id = "uchat-ignoring")
    public String ignoringPlayer(@Source CommandSource p) {
        if (UChat.get().ignoringPlayer.containsKey(p.getName())) {
            return Arrays.toString(UChat.get().ignoringPlayer.get(p.getName()).toArray());
        }
        return "--";
    }

    @Placeholder(id = "uchat-defaultchannel")
    public String defaultChannel(@Source CommandSource p) {
        return UChat.get().getDefChannel(p instanceof Player ? ((Player) p).getWorld().getName() : null).getName();
    }
} public interface UCVHelper {
    Cause getCause(CommandSource src);

    Cause getCause(PluginContainer instance);

    StringBuilder getEnchantments(StringBuilder sb, ItemStack item);

    ItemStack getItemInHand(Player sender);

    ItemType getItemName(ItemStack itemStack);
}
import br.net.fabiozumbi12.UltimateChat.Sponge.API.UChatReloadEvent;
import br.net.fabiozumbi12.UltimateChat.Sponge.API.uChatAPI;
import br.net.fabiozumbi12.UltimateChat.Sponge.Bungee.UChatBungee;
import br.net.fabiozumbi12.UltimateChat.Sponge.config.UCConfig;
import br.net.fabiozumbi12.UltimateChat.Sponge.config.UCLang;
import br.net.fabiozumbi12.UltimateChat.Sponge.config.VersionData;
import br.net.fabiozumbi12.UltimateChat.Sponge.discord.UCDInterface;
import br.net.fabiozumbi12.UltimateChat.Sponge.discord.UCDiscord;
import br.net.fabiozumbi12.UltimateChat.Sponge.discord.UCDiscordSync;
import br.net.fabiozumbi12.UltimateChat.Sponge.jedis.UCJedisLoader;
import br.net.fabiozumbi12.UltimateChat.Sponge.util.UCLogger;
import br.net.fabiozumbi12.UltimateChat.Sponge.util.UCPerms;
import br.net.fabiozumbi12.UltimateChat.Sponge.util.UCPlaceHoldersRelational;
import br.net.fabiozumbi12.UltimateChat.Sponge.util.UCVHelper;
import com.google.inject.Inject;
import ninja.leaping.configurate.objectmapping.GuiceObjectMapperFactory;
import org.spongepowered.api.Game;
import org.spongepowered.api.Platform.Component;
import org.spongepowered.api.Server;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.config.ConfigDir;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.GameReloadEvent;
import org.spongepowered.api.event.game.state.GamePostInitializationEvent;
import org.spongepowered.api.event.game.state.GameStartedServerEvent;
import org.spongepowered.api.event.game.state.GameStoppedServerEvent;
import org.spongepowered.api.event.game.state.GameStoppingServerEvent;
import org.spongepowered.api.event.service.ChangeServiceProviderEvent;
import org.spongepowered.api.plugin.Dependency;
import org.spongepowered.api.plugin.Plugin;
import org.spongepowered.api.plugin.PluginContainer;
import org.spongepowered.api.scheduler.Task;
import org.spongepowered.api.service.economy.EconomyService;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Path;
import java.util.*;
/*
 Copyright @FabioZumbi12

 This class is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any
  damages arising from the use of this class.

 Permission is granted to anyone to use this class for any purpose, including commercial plugins, and to alter it and
 redistribute it freely, subject to the following restrictions:
 1 - The origin of this class must not be misrepresented; you must not claim that you wrote the original software. If you
 use this class in other plugins, an acknowledgment in the plugin documentation would be appreciated but is not required.
 2 - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original class.
 3 - This notice may not be removed or altered from any source distribution.

 Esta classe é fornecida "como está", sem qualquer garantia expressa ou implícita. Em nenhum caso os autores serão
 responsabilizados por quaisquer danos decorrentes do uso desta classe.

 É concedida permissão a qualquer pessoa para usar esta classe para qualquer finalidade, incluindo plugins pagos, e para
 alterá-lo e redistribuí-lo livremente, sujeito às seguintes restrições:
 1 - A origem desta classe não deve ser deturpada; você não deve afirmar que escreveu a classe original. Se você usar esta
  classe em um plugin, uma confirmação de autoria na documentação do plugin será apreciada, mas não é necessária.
 2 - Versões de origem alteradas devem ser claramente marcadas como tal e não devem ser deturpadas como sendo a
 classe original.
 3 - Este aviso não pode ser removido ou alterado de qualquer distribuição de origem.
 */

package br.net.fabiozumbi12.UltimateChat.Sponge;



@Plugin(id = "ultimatechat",
        name = "UltimateChat",
        version = VersionData.VERSION,
        authors = "FabioZumbi12",
        description = "Complete and advanced chat plugin",
        dependencies = {
                @Dependency(id = "jdalibraryloader", optional = true),
                @Dependency(id = "mcclans", optional = true),
                @Dependency(id = "placeholderapi", optional = true),
                @Dependency(id = "pixelmon", optional = true),
                @Dependency(id = "nucleus", optional = true)})
public class UChat {

    private static UChat uchat;
    @Inject
    public GuiceObjectMapperFactory factory;
    public Map<String, String> tempChannels = Collections.synchronizedMap(new HashMap<>());
    public Map<String, String> tellPlayers = Collections.synchronizedMap(new HashMap<>());
    public Map<String, String> tempTellPlayers = Collections.synchronizedMap(new HashMap<>());
    public Map<String, String> respondTell = Collections.synchronizedMap(new HashMap<>());
    public List<String> mutes = Collections.synchronizedList(new ArrayList<>());
    public List<String> isSpy = Collections.synchronizedList(new ArrayList<>());
    public List<String> command = Collections.synchronizedList(new ArrayList<>());
    public HashMap<String, Integer> timeMute = new HashMap<>();
    public List<String> msgTogglePlayers = Collections.synchronizedList(new ArrayList<>());
    public HashMap<String, List<String>> ignoringPlayer = new HashMap<>();
    private UCLogger logger;
    @Inject
    @ConfigDir(sharedRoot = false)
    private Path configDir;
    @Inject
    private Game game;
    @Inject
    private PluginContainer plugin;
    private UCConfig config;
    private UCCommands cmds;
    private Server serv;
    private EconomyService econ;
    private UCPerms perms;
    private uChatAPI ucapi;
    private UCDInterface UCJDA;
    private UCLang lang;