package moe.oko.Kiafumi; import moe.oko.Kiafumi.command.CommandClass; import moe.oko.Kiafumi.command.CommandRegistrar; import moe.oko.Kiafumi.listener.MainListener; import moe.oko.Kiafumi.listener.SkynetListener; import moe.oko.Kiafumi.model.KiafumiDB; import moe.oko.Kiafumi.model.ServerManager; import moe.oko.Kiafumi.util.CommandInfo; import moe.oko.Kiafumi.util.EmbedUI; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDABuilder; import net.dv8tion.jda.api.OnlineStatus; import net.dv8tion.jda.api.entities.Activity; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; import net.dv8tion.jda.api.requests.GatewayIntent; import net.dv8tion.jda.api.utils.ChunkingFilter; import net.dv8tion.jda.api.utils.MemberCachePolicy; import net.dv8tion.jda.api.utils.cache.CacheFlag; import org.simpleyaml.configuration.file.YamlConfiguration; import java.io.File; import java.io.FileNotFoundException; import java.io.InputStream; import java.nio.file.Files; import java.time.ZonedDateTime; import java.util.List; import static moe.oko.Kiafumi.util.LoggingManager.*; /** * Kiafumi Main Class * @author Kay, oko, Tiddy * @version 0.9.0-pre * @apiNote Thanks to: * | Maxopoly, Orinnari, ProgrammerDan, and more, for helping teach Kay how to code Java from scratch. * | Favna, and the HC Development community for encouraging the development core of HC. * | HC as a whole, for being a wonderful community and encouraging the creation of this bot * | Civ, for encouraging us all to sort out how the hell programming works * | Discord, for offering a platform to learn these skills on * | The Java Development Team, for making a Language I personally love and adore - Kay */ public class Kiafumi { private final File CONFIG_FILE = new File("config.yml"); public YamlConfiguration yamlConfiguration = new YamlConfiguration(); public String footer = EmbedUI.BRAND + " 0.9.0-pre"; public static Kiafumi instance; public static JDA JDA; public List activeCommands; public KiafumiConfig config; public KiafumiDB database; public ServerManager serverManager; /** * Main Class function. * @param args - Arguments for program start. */ public static void main(String[] args) { try { var kia = new Kiafumi(); kia.start(); } catch (Exception ex) { System.out.println("Failed to start Kiafumi, check your Java installation."); ex.printStackTrace(); } } /** * Ran on program start. Anything in here can determine whether the program will start. */ public void start() { instance = this; info("Starting Kiafumi."); // All commands to be loaded on startup! activeCommands = new CommandRegistrar().getCommandClasses(); // Ensuring the configuration file is generated and/or exists. if (!CONFIG_FILE.exists()) { try (InputStream is = this.getClass().getClassLoader().getResourceAsStream("config.yml")) { // Save the default config Files.copy(is, CONFIG_FILE.toPath()); } catch (Exception ex) { warn("Failed to create the configuration file. Stopping. (" + CONFIG_FILE.getAbsolutePath() + ")"); ex.printStackTrace(); return; } } // Try to load configuration into the configuration API. try { yamlConfiguration.load("config.yml"); } catch (FileNotFoundException e) { e.printStackTrace(); warn("File not found, must've failed to create..."); } catch (Exception e) { warn("Ensure all values are inputted properly."); e.printStackTrace(); } // Initializes our configuration helper & ensures it loads properly. config = new KiafumiConfig(yamlConfiguration); if(config.load()) { info("Fetched Kiafumi config."); } else { error("Failed to load configuration. Stopping process."); Runtime.getRuntime().exit(0); } // Registers the stop() function if the program is stopped. info("Registering shutdown hook."); Runtime.getRuntime().addShutdownHook(new Thread(this::stop)); // Initializes database and loads credentials. database = config.createDb(); serverManager = new ServerManager(); // Makes our JDA instance. startDiscord(); } /** * Ran on program shutdown. */ public void stop() { var build = new EmbedBuilder() .setColor(EmbedUI.FAILURE) .setTitle("Offline") .setFooter(footer) .setTimestamp(ZonedDateTime.now()); JDA.getTextChannelById(config.getLogChannel()).sendMessageEmbeds(build.build()).queue(); info("Shutting down Kiafumi."); if(database.saveServerInformation()) { info("Successfully saved server information. Shutting down peacefully."); } else { for(int i = 0; i < 15; i++) { error("FAILED TO SAVE SERVER INFORMATION."); } } } /** * Starts a JDA Instance. */ public void startDiscord() { try { JDA = JDABuilder.create(config.getToken(), GatewayIntent.GUILD_MEMBERS, GatewayIntent.GUILD_MESSAGES, GatewayIntent.DIRECT_MESSAGES, GatewayIntent.GUILD_PRESENCES, GatewayIntent.GUILD_VOICE_STATES) .setChunkingFilter(ChunkingFilter.ALL) .setMemberCachePolicy(MemberCachePolicy.ALL) .disableCache(CacheFlag.EMOTE) .setActivity(Activity.of(Activity.ActivityType.valueOf(config.getActivityType()), config.getActivityMsg())) .setStatus(OnlineStatus.valueOf(config.getStatusType())) .addEventListeners( new MainListener(), new SkynetListener() ).build().awaitReady(); } catch(Exception ex) { error("Initialization broke..."); ex.printStackTrace(); return; } registerAllCommands(); info("Finished registering commands."); var eb = new EmbedBuilder() .setColor(EmbedUI.SUCCESS) .setTitle("Online") .setFooter(footer) .setTimestamp(ZonedDateTime.now()); JDA.getTextChannelById(config.getLogChannel()).sendMessageEmbeds(eb.build()).queue(); } /** * Quick method to register commands in all servers. */ private void registerAllCommands() { // Registers slash commands. info("Registering commands for guilds."); int i = 0; for(Guild guild : JDA.getGuilds()) { registerForGuild(guild); i++; } info("Registered commands for " + i + " guilds."); // Registers the event listeners for those commands. for(CommandClass cmd : activeCommands) { JDA.addEventListener(cmd); } } /** * Registers all bot commands with the guild provided * @param guild - guild to have commands provided to */ public void registerForGuild(Guild guild) { debug("Registering commands for guild [" + guild.getName() + ":" + guild.getId() + "]"); int i = 0; int j = 0; for(CommandClass cmd : activeCommands) { for(CommandInfo ci : cmd.getSlashCommandInfo()) { var cca = guild.upsertCommand(ci.getName(), ci.getDescription()); i++; if(ci.hasSubCommands()) { for (String name : ci.getSubCommands().keySet()) { var si = ci.getSubCommands().get(name); var sd = new SubcommandData(si.getName(), si.getDescription()); for (String option : si.getOptions().keySet()) { sd.addOption(si.getOptions().get(option), option, si.getOptionDescriptions().get(option), si.getOptionRequirements().get(option)); } cca.addSubcommands(sd); } } if(ci.hasOptions()) { for(String name : ci.getOptions().keySet()) { // Any intelligent IDE will rage about the option not being used, it's added to the action then executed later, don't edit without reason. cca.addOption(ci.getOptions().get(name), name, ci.getOptionDescriptions().get(name), ci.getOptionRequirements().get(name)); } } // Push w/ modifications. //trace("Command: " + ci.getName() + " registration on " + guild.getId() + " completed."); try { cca.queue(); } catch (Exception ex) { // Only time this should occur is when a server does not have the proper scope. error("Failed to queue command for RestAction."); } } } debug("Registered " + i + " commands for guild [" + guild.getId() + "]"); } // Gets the active database. public KiafumiDB getDatabase() { return database; } // Gets active ServerManager public ServerManager getServerManager() { return serverManager; } }