-
-
Notifications
You must be signed in to change notification settings - Fork 109
Expand file tree
/
Copy pathApplication.java
More file actions
143 lines (120 loc) · 5.62 KB
/
Application.java
File metadata and controls
143 lines (120 loc) · 5.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package org.togetherjava.tjbot;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.exceptions.InvalidTokenException;
import net.dv8tion.jda.api.requests.GatewayIntent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.togetherjava.tjbot.config.Config;
import org.togetherjava.tjbot.db.Database;
import org.togetherjava.tjbot.features.Features;
import org.togetherjava.tjbot.features.SlashCommandAdapter;
import org.togetherjava.tjbot.features.system.BotCore;
import org.togetherjava.tjbot.logging.LogMarkers;
import org.togetherjava.tjbot.logging.discord.DiscordLogging;
import org.togetherjava.tjbot.secrets.Secrets;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.SQLException;
/**
* Main class of the application. Use {@link #main(String[])} to start an instance of it.
* <p>
* New commands can be created by implementing {@link SlashCommandInteractionEvent} or extending
* {@link SlashCommandAdapter}. They can then be registered in {@link Features}.
*/
public class Application {
private Application() {
throw new UnsupportedOperationException("Utility class, construction not supported");
}
private static final Logger logger = LoggerFactory.getLogger(Application.class);
private static final String DEFAULT_CONFIG_PATH = "/config.json";
private static final String DEFAULT_SECRETS_PATH = "secrets.json";
/**
* Starts the application.
*
* @param args command line arguments - [the path to the configuration file (optional, by
* default "config.json")]
*/
public static void main(final String[] args) {
if (args.length > 1) {
throw new IllegalArgumentException("Expected no or one argument but " + args.length
+ " arguments were provided. The first argument is the path to the configuration file. If no argument was provided, '"
+ DEFAULT_CONFIG_PATH + "' will be assumed.");
}
String configPath = args.length == 1 ? args[0] : DEFAULT_CONFIG_PATH;
Config config;
try (InputStream stream = Application.class.getResourceAsStream(configPath)) {
if (stream == null) {
throw new IOException("InputStream is null when loading " + configPath);
}
config = Config.load(new String(stream.readAllBytes(), StandardCharsets.UTF_8));
} catch (IOException e) {
logger.error("Unable to load the configuration file from path '{}'", configPath, e);
return;
}
Path secretsPath = Path.of(args.length == 1 ? args[0] : DEFAULT_SECRETS_PATH);
Secrets secrets;
try {
secrets = Secrets.load(secretsPath);
} catch (IOException e) {
logger.error("Unable to load the configuration file from path '{}'",
secretsPath.toAbsolutePath(), e);
return;
}
Thread.setDefaultUncaughtExceptionHandler(Application::onUncaughtException);
Runtime.getRuntime().addShutdownHook(new Thread(Application::onShutdown));
DiscordLogging.startDiscordLogging(config, secrets);
runBot(config, secrets);
}
/**
* Runs an instance of the bot, connecting to the given token and using the given database.
*
* @param config the configuration to run the bot with
* @param secrets the secrets to run the bot with
*/
@SuppressWarnings("WeakerAccess")
public static void runBot(Config config, Secrets secrets) {
logger.info("Starting bot...");
Path databasePath = Path.of(config.getDatabasePath());
try {
Path parentDatabasePath = databasePath.toAbsolutePath().getParent();
if (parentDatabasePath != null) {
Files.createDirectories(parentDatabasePath);
}
Database database = new Database("jdbc:sqlite:" + databasePath.toAbsolutePath());
JDA jda = JDABuilder.createDefault(secrets.getToken())
.enableIntents(GatewayIntent.GUILD_MEMBERS, GatewayIntent.MESSAGE_CONTENT)
.build();
jda.awaitReady();
BotCore core = new BotCore(jda, database, config, secrets);
CommandReloading.reloadCommands(jda, core);
core.scheduleRoutines(jda);
jda.addEventListener(core);
logger.info("Bot is ready");
} catch (InvalidTokenException e) {
logger.error(LogMarkers.SENSITIVE, "Failed to login", e);
} catch (InterruptedException e) {
logger.error("Interrupted while waiting for setup to complete", e);
Thread.currentThread().interrupt();
} catch (SQLException e) {
logger.error("Failed to create database", e);
} catch (IOException e) {
logger.error("Failed to create path to the database at: {}",
databasePath.toAbsolutePath(), e);
}
}
private static void onShutdown() {
// This may be called during JVM shutdown via a hook and hence only has minimal time to
// react.
// There is no guarantee that this method can be executed fully - it should run as
// fast as possible and only do the minimal necessary actions.
logger.info("Bot has been stopped");
}
private static void onUncaughtException(Thread failingThread, Throwable failure) {
logger.error("Unknown error in thread {}.", failingThread.getName(), failure);
}
}