package me.chayapak1.chomens_bot; import com.google.common.util.concurrent.ThreadFactoryBuilder; import me.chayapak1.chomens_bot.plugins.ConsolePlugin; import me.chayapak1.chomens_bot.plugins.DiscordPlugin; import me.chayapak1.chomens_bot.plugins.IRCPlugin; import me.chayapak1.chomens_bot.plugins.LoggerPlugin; import me.chayapak1.chomens_bot.util.*; import net.dv8tion.jda.api.requests.restaction.MessageCreateAction; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.Constructor; import java.io.*; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class Main { public static final List bots = new ArrayList<>(); public static final ExecutorService executorService = Executors.newFixedThreadPool( Runtime.getRuntime().availableProcessors(), new ThreadFactoryBuilder().setNameFormat("ExecutorService #%d").build() ); public static final ScheduledExecutorService executor = Executors.newScheduledThreadPool( Runtime.getRuntime().availableProcessors(), new ThreadFactoryBuilder().setNameFormat("ScheduledExecutorService #%d").build() ); private static Configuration config; private static boolean alreadyStarted = false; private static boolean stopping = false; private static int backupFailTimes = 0; private static DiscordPlugin discord; public static void main(String[] args) throws IOException { final Path configPath = Path.of("config.yml"); final Constructor constructor = new Constructor(Configuration.class, new LoaderOptions()); final Yaml yaml = new Yaml(constructor); if (!Files.exists(configPath)) { // creates config file from default-config.yml InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("default-config.yml"); if (is == null) System.exit(1); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); StringBuilder stringBuilder = new StringBuilder(); while (reader.ready()) { char character = (char) reader.read(); stringBuilder.append(character); } String defaultConfig = stringBuilder.toString(); // writes it BufferedWriter configWriter = Files.newBufferedWriter(configPath); configWriter.write(defaultConfig); configWriter.close(); LoggerUtilities.info("config.yml file was not found, so the default one was created. Please modify it to your needs."); System.exit(1); } InputStream opt = Files.newInputStream(configPath); BufferedReader reader = new BufferedReader(new InputStreamReader(opt)); config = yaml.load(reader); if (!config.backup.enabled) { initializeBots(); } else { executor.scheduleAtFixedRate(() -> { boolean reachable; try { HttpUtilities.getRequest(new URL(config.backup.address)); reachable = true; } catch (Exception e) { reachable = false; } if (!reachable && !alreadyStarted) { backupFailTimes++; if (backupFailTimes > config.backup.failTimes) { LoggerUtilities.info("Main instance is down! Starting backup instance"); initializeBots(); } } else if (reachable && alreadyStarted) { LoggerUtilities.info("Main instance is back up! Now stopping"); // no need to reset backupFailTimes because we are stopping anyway stop(); } }, 0, config.backup.interval, TimeUnit.MILLISECONDS); } } public static void initializeBots() { alreadyStarted = true; try { final Configuration.BotOption[] botsOptions = config.bots; for (Configuration.BotOption botOption : botsOptions) { final Bot bot = new Bot(botOption, bots, config); bots.add(bot); } // initialize util classes and plugins PersistentDataUtilities.init(); ComponentUtilities.init(); new ConsolePlugin(); LoggerPlugin.init(); if (config.discord.enabled) discord = new DiscordPlugin(config); if (config.irc.enabled) new IRCPlugin(config); LoggerUtilities.info("Initialized all bots. Now connecting"); for (Bot bot : bots) bot.connect(); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } // most of these are stolen from HBot public static void stop () { if (stopping) return; stopping = true; executor.shutdown(); PersistentDataUtilities.stop(); executorService.shutdown(); try { final boolean ignoredExecutorDone = executor.awaitTermination(5, TimeUnit.SECONDS); final boolean ignoredExecutorServiceDone = executorService.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException ignored) {} ArrayList copiedList; synchronized (bots) { copiedList = new ArrayList<>(bots); } final boolean ircEnabled = config.irc.enabled; final boolean discordEnabled = config.discord.enabled; final boolean[] stoppedDiscord = new boolean[copiedList.size()]; int botIndex = 0; for (Bot bot : copiedList) { try { if (discordEnabled) { final String channelId = bot.discord.servers.get(bot.host + ":" + bot.port); final MessageCreateAction messageAction = bot.discord.sendMessageInstantly("Stopping..", channelId, false); final int finalBotIndex = botIndex; messageAction.queue( (message) -> stoppedDiscord[finalBotIndex] = true, (error) -> stoppedDiscord[finalBotIndex] = true // should i also set this to true on fail? ); } if (ircEnabled) bot.irc.quit("Stopping.."); bot.stop(); } catch (Exception ignored) {} botIndex++; } if (discordEnabled) { discord.jda.shutdown(); for (int i = 0; i < 150; i++) { try { if (!ArrayUtilities.isAllTrue(stoppedDiscord)) Thread.sleep(50); else break; } catch (InterruptedException ignored) {} } } System.exit(0); } }