Skip to content
This repository was archived by the owner on Dec 1, 2021. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ dependencies {
implementation 'com.h2database:h2:1.4.200'
implementation 'com.zaxxer:HikariCP:5.0.0'

implementation 'org.quartz-scheduler:quartz:2.3.2'

// Lombok annotations
compileOnly 'org.projectlombok:lombok:1.18.22'
annotationProcessor 'org.projectlombok:lombok:1.18.22'
testCompileOnly 'org.projectlombok:lombok:1.18.22'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.22'

// Logging
implementation 'ch.qos.logback:logback-classic:1.2.6'
implementation 'ch.qos.logback:logback-classic:1.2.7'

// JUnit tests
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
Expand Down
41 changes: 11 additions & 30 deletions src/main/java/net/javadiscord/javabot2/Bot.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@
import net.javadiscord.javabot2.command.SlashCommandListener;
import net.javadiscord.javabot2.config.BotConfig;
import net.javadiscord.javabot2.db.DbHelper;
import net.javadiscord.javabot2.systems.moderation.ModerationService;
import net.javadiscord.javabot2.tasks.ScheduledTasks;
import org.javacord.api.DiscordApi;
import org.javacord.api.DiscordApiBuilder;
import org.javacord.api.entity.intent.Intent;
import org.quartz.SchedulerException;

import java.nio.file.Path;
import java.time.ZoneOffset;
import java.util.TimeZone;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
* The main program entry point.
Expand All @@ -32,22 +32,6 @@ public class Bot {
*/
public static HikariDataSource hikariDataSource;

/**
* A thread-safe MongoDB client that can be used to interact with MongoDB.
* @deprecated Use the relational data source for all future persistence
* needs; it promotes more organized code that's less prone to failures.
*/
@Deprecated
public static MongoClient mongoClient;

/**
* The single Mongo database where all bot data is stored.
* @deprecated Use the relational data source for all future persistence
* needs; it promotes more organized code that's less prone to failures.
*/
@Deprecated
public static MongoDatabase mongoDb;

/**
* The bot's configuration.
*/
Expand Down Expand Up @@ -81,7 +65,13 @@ public static void main(String[] args) {
"commands/moderation.yaml"
);
api.addSlashCommandCreateListener(commandListener);
initScheduledTasks(api);
try {
ScheduledTasks.init(api);
log.info("Initialized scheduled tasks.");
} catch (SchedulerException e) {
log.error("Could not initialize all scheduled tasks.", e);
api.disconnect().join();
}
}

/**
Expand All @@ -95,24 +85,15 @@ private static void initDataSources() {
throw new IllegalStateException("Missing required Discord bot token! Please edit config/systems.json to add it, then run again.");
}
hikariDataSource = DbHelper.initDataSource(config);
mongoDb = initMongoDatabase();
}

@Deprecated
private static MongoDatabase initMongoDatabase() {
mongoClient = new MongoClient(new MongoClientURI(config.getSystems().getMongoDatabaseUrl()));
var mongoClient = new MongoClient(new MongoClientURI(config.getSystems().getMongoDatabaseUrl()));
var db = mongoClient.getDatabase("javabot");
var warnCollection = db.getCollection("warn");
warnCollection.createIndex(Indexes.ascending("userId"), new IndexOptions().unique(false));
warnCollection.createIndex(Indexes.descending("createdAt"), new IndexOptions().unique(false));
return db;
}

private static void initScheduledTasks(DiscordApi api) {
// Regularly check for and unmute users whose mutes have expired.
asyncPool.scheduleAtFixedRate(() -> {
for (var server : api.getServers()) {
new ModerationService(api, config.get(server).getModeration()).unmuteExpired();
}
}, 1L, 1L, TimeUnit.MINUTES);
}
}
73 changes: 73 additions & 0 deletions src/main/java/net/javadiscord/javabot2/tasks/ScheduledTasks.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package net.javadiscord.javabot2.tasks;

import net.javadiscord.javabot2.tasks.jobs.DiscordApiJob;
import net.javadiscord.javabot2.tasks.jobs.UnmuteExpiredJob;
import org.javacord.api.DiscordApi;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

/**
* This class is responsible for setting up all scheduled tasks that the bot
* should run periodically, using the Quartz {@link Scheduler}. To add new tasks
* to the schedule, add them to the {@link ScheduledTasks#scheduleAllTasks(Scheduler, DiscordApi)}
* method.
*/
public class ScheduledTasks {
// Hide the constructor.
private ScheduledTasks() {}

/**
* Initializes all scheduled jobs and starts the scheduler. Also adds a
* shutdown hook that gracefully stops the scheduler when the program ends.
* @param api The Discord API, which may be needed by some jobs.
* @throws SchedulerException If an error occurs while starting the scheduler.
*/
public static void init(DiscordApi api) throws SchedulerException {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduleAllTasks(scheduler, api);
scheduler.start();
// Add a hook to shut down the scheduler cleanly when the program terminates.
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
scheduler.shutdown();
} catch (SchedulerException e) {
e.printStackTrace();
}
}));
}

/**
* This method is where all tasks are scheduled. <strong>Add scheduled tasks
* to the scheduler using this method!</strong>
* @param scheduler The scheduler to use.
* @param api The Discord API.
* @throws SchedulerException If an error occurs while adding a task.
*/
private static void scheduleAllTasks(Scheduler scheduler, DiscordApi api) throws SchedulerException {
scheduleApiJob(scheduler, api, UnmuteExpiredJob.class, SimpleScheduleBuilder.repeatMinutelyForever());
}

/**
* Convenience method for scheduling an API-dependent job using a single
* trigger that follows a given schedule.
* @param scheduler The scheduler to add the job to.
* @param api The Discord API.
* @param type The type of job to schedule.
* @param scheduleBuilder A schedule builder that the trigger will use.
* @throws SchedulerException If an error occurs while adding the job.
* @see SimpleScheduleBuilder
* @see CronScheduleBuilder
* @see CalendarIntervalScheduleBuilder
*/
private static void scheduleApiJob(
Scheduler scheduler,
DiscordApi api,
Class<? extends DiscordApiJob> type,
ScheduleBuilder<?> scheduleBuilder
) throws SchedulerException {
scheduler.scheduleJob(
DiscordApiJob.build(type, api),
TriggerBuilder.newTrigger().withSchedule(scheduleBuilder).build()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package net.javadiscord.javabot2.tasks.jobs;

import org.javacord.api.DiscordApi;
import org.quartz.*;

import java.util.Map;

/**
* A type of job which requires a reference to a {@link DiscordApi} to be
* available at execution time. Extend this class if your job needs the api.
*/
public abstract class DiscordApiJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
DiscordApi api = (DiscordApi) context.getJobDetail().getJobDataMap().get("discord-api");
execute(context, api);
}

protected abstract void execute(JobExecutionContext context, DiscordApi api) throws JobExecutionException;

/**
* Builder method that produces a {@link JobDetail} for the given job type,
* with job data initialized to include a reference to the given Discord API.
* @param jobType The type of job to create a job detail for.
* @param api The Discord API.
* @return The created job detail.
*/
public static JobDetail build(Class<? extends DiscordApiJob> jobType, DiscordApi api) {
return JobBuilder.newJob(jobType)
.usingJobData(new JobDataMap(Map.of("discord-api", api)))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package net.javadiscord.javabot2.tasks.jobs;

import net.javadiscord.javabot2.Bot;
import net.javadiscord.javabot2.systems.moderation.ModerationService;
import org.javacord.api.DiscordApi;
import org.quartz.JobExecutionContext;

/**
* Job which unmutes users whose mutes have expired.
*/
public class UnmuteExpiredJob extends DiscordApiJob {
@Override
protected void execute(JobExecutionContext context, DiscordApi api) {
for (var server : api.getServers()) {
new ModerationService(api, Bot.config.get(server).getModeration()).unmuteExpired();
}
}
}
2 changes: 2 additions & 0 deletions src/main/resources/quartz.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
org.quartz.threadPool.threadCount = 4
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore