Butler.java
package com.niklasarndt.discordbutler;
import com.niklasarndt.discordbutler.listener.ApiConnectedListener;
import com.niklasarndt.discordbutler.listener.MessageListener;
import com.niklasarndt.discordbutler.listener.ReactionListener;
import com.niklasarndt.discordbutler.scheduler.ScheduleManager;
import com.niklasarndt.discordbutler.util.ButlerUtils;
import com.niklasarndt.discordbutler.util.ExecutionFlags;
import io.sentry.Sentry;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.utils.cache.CacheFlag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.security.auth.login.LoginException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
/**
* Created by Niklas on 2020/07/24.
*/
public class Butler {
private static final Logger logger = LoggerFactory.getLogger(Butler.class);
public static void main(String[] args) {
if (System.getenv("SENTRY_DSN") != null || System.getProperty("sentry.dsn") != null) {
Sentry.init();
logger.info("Has Sentry been initialized correctly? {}", Sentry.isInitialized());
} else {
logger.info("Specify your DSN via SENTRY_DSN to enable Sentry logging.");
}
try {
new Butler(buildEnvironmentFlags(args));
} catch (Exception e) {
logger.error("Encountered uncaught exception", e);
logger.error("This fatal exception will lead to an application shutdown.");
System.exit(1);
}
}
private static String[] buildEnvironmentFlags(String[] args) {
String[] envs = Optional.ofNullable(System.getenv("EXECUTION_FLAGS"))
.orElse("").split(",");
boolean oneOnly = envs.length == 0 || args.length == 0;
String[] combined;
if (oneOnly) combined = envs.length != 0 ? envs : args;
else combined = new String[envs.length + args.length];
if (!oneOnly) {
System.arraycopy(envs, 0, combined, 0, envs.length);
System.arraycopy(args, 0, combined, envs.length, args.length);
}
return combined;
}
private final long startupTimestamp = System.currentTimeMillis();
private final List<ExecutionFlags> flags;
private long ownerId;
private JDA jda;
private ModuleManager moduleManager;
private ScheduleManager scheduleManager;
protected Butler() throws LoginException {
this(ExecutionFlags.NONE);
}
protected Butler(String... flags) throws LoginException {
this(Arrays.stream(flags)
.map(el -> ButlerUtils.parseInt(el, 0))
.toArray(Integer[]::new));
}
protected Butler(Integer... flags) throws LoginException {
this(ExecutionFlags.getFlagsById(flags));
}
protected Butler(ExecutionFlags... flags) throws LoginException {
logger.info("Startup is in progress");
this.flags = Collections.unmodifiableList(List.of(flags));
if (hasFlag(ExecutionFlags.NO_API_CONNECTION)) {
logger.warn("NO_API_CONNECTION: App will not be kept alive by daemon.");
} else {
loadOwnerId();
jda = setUpJda();
logger.info("JDA has been set up!");
}
if (!hasFlag(ExecutionFlags.NO_MODULE_MANAGER)) {
moduleManager = new ModuleManager(this);
moduleManager.loadAll();
logger.info("Module manager has been set up!");
}
if (!hasFlag(ExecutionFlags.NO_SCHEDULE_MANAGER)) {
scheduleManager = new ScheduleManager(this);
logger.info("Schedule manager has been set up!");
}
logger.info("SUCCESS: SETUP COMPLETE");
logger.info("EXEC FLAGS: {}", ExecutionFlags.prettyPrint(
this.flags.toArray(new ExecutionFlags[0])));
}
private void loadOwnerId() {
try {
ownerId = Long.parseLong(System.getenv("OWNER_ID"));
} catch (Exception e) {
throw new IllegalArgumentException(
"You must provide the owner's Discord ID via OWNER_ID.");
}
logger.info("Owner ID has been retrieved.");
}
/**
* Sets up the JDA instance.
*
* @return The completely initialized JDA instance.
*
* @throws LoginException Will cause a shutdown + sentry log.
*/
private JDA setUpJda() throws LoginException {
final JDABuilder builder = JDABuilder.create(System.getenv("TOKEN_DISCORD"),
GatewayIntent.DIRECT_MESSAGES, GatewayIntent.DIRECT_MESSAGE_REACTIONS,
GatewayIntent.GUILD_MESSAGES, GatewayIntent.GUILD_MESSAGE_REACTIONS);
builder.setActivity(Activity.of(Activity.ActivityType.WATCHING, "via direct messages"));
builder.addEventListeners(new ApiConnectedListener(this),
new MessageListener(this), new ReactionListener(this));
builder.disableCache(CacheFlag.ACTIVITY, CacheFlag.VOICE_STATE,
CacheFlag.EMOTE, CacheFlag.CLIENT_STATUS);
return builder.build();
}
public JDA getJda() {
return jda;
}
public long getOwnerId() {
return ownerId;
}
public long getStartupTimestamp() {
return startupTimestamp;
}
public ModuleManager getModuleManager() {
return moduleManager;
}
public ScheduleManager getScheduleManager() {
return scheduleManager;
}
public List<ExecutionFlags> getFlags() {
return flags;
}
public boolean hasFlag(int flagId) {
return hasFlag(ExecutionFlags.getFlagById(flagId));
}
public boolean hasFlag(ExecutionFlags flag) {
return flags.contains(flag);
}
public void shutdown(int exitCode) {
new Thread(null, () -> {
logger.info("Shutdown in 5 seconds!");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("Initiating shutdown...");
moduleManager.unloadAll();
jda.shutdown();
logger.info("The connection to the Discord API was shut down. Goodbye!");
System.exit(exitCode);
}, "terminator").start();
}
}