From 1bce1a48da821e78c3dbadd114aa51cc1c762960 Mon Sep 17 00:00:00 2001 From: Seb Date: Tue, 7 Jun 2022 20:50:21 +0100 Subject: [PATCH] feat(core): Added DelayedTaskService and Remindme command (#105) --- src/main/java/de/voidtech/gerald/Gerald.java | 2 +- .../de/voidtech/gerald/annotations/Task.java | 15 ++ .../commands/utils/RemindMeCommand.java | 154 ++++++++++++++++++ .../voidtech/gerald/entities/DelayedTask.java | 74 +++++++++ .../gerald/service/DelayedTaskService.java | 122 ++++++++++++++ .../gerald/service/RemindMeService.java | 59 +++++++ .../voidtech/gerald/tasks/AbstractTask.java | 21 +++ .../voidtech/gerald/tasks/RemindMeTask.java | 31 ++++ .../de/voidtech/gerald/tasks/TaskType.java | 15 ++ 9 files changed, 492 insertions(+), 1 deletion(-) create mode 100644 src/main/java/de/voidtech/gerald/annotations/Task.java create mode 100644 src/main/java/de/voidtech/gerald/commands/utils/RemindMeCommand.java create mode 100644 src/main/java/de/voidtech/gerald/entities/DelayedTask.java create mode 100644 src/main/java/de/voidtech/gerald/service/DelayedTaskService.java create mode 100644 src/main/java/de/voidtech/gerald/service/RemindMeService.java create mode 100644 src/main/java/de/voidtech/gerald/tasks/AbstractTask.java create mode 100644 src/main/java/de/voidtech/gerald/tasks/RemindMeTask.java create mode 100644 src/main/java/de/voidtech/gerald/tasks/TaskType.java diff --git a/src/main/java/de/voidtech/gerald/Gerald.java b/src/main/java/de/voidtech/gerald/Gerald.java index 99140a0b..ec83e68a 100644 --- a/src/main/java/de/voidtech/gerald/Gerald.java +++ b/src/main/java/de/voidtech/gerald/Gerald.java @@ -45,7 +45,7 @@ @SpringBootApplication public class Gerald { - @Bean + @Bean("JDA") @DependsOn(value = {"sessionFactory"}) @Order(3) @Autowired diff --git a/src/main/java/de/voidtech/gerald/annotations/Task.java b/src/main/java/de/voidtech/gerald/annotations/Task.java new file mode 100644 index 00000000..4dc7e7cf --- /dev/null +++ b/src/main/java/de/voidtech/gerald/annotations/Task.java @@ -0,0 +1,15 @@ +package main.java.de.voidtech.gerald.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.stereotype.Component; + +@Component +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Task { + +} diff --git a/src/main/java/de/voidtech/gerald/commands/utils/RemindMeCommand.java b/src/main/java/de/voidtech/gerald/commands/utils/RemindMeCommand.java new file mode 100644 index 00000000..9abeee00 --- /dev/null +++ b/src/main/java/de/voidtech/gerald/commands/utils/RemindMeCommand.java @@ -0,0 +1,154 @@ +package main.java.de.voidtech.gerald.commands.utils; + +import java.awt.Color; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; + +import main.java.de.voidtech.gerald.annotations.Command; +import main.java.de.voidtech.gerald.commands.AbstractCommand; +import main.java.de.voidtech.gerald.commands.CommandCategory; +import main.java.de.voidtech.gerald.commands.CommandContext; +import main.java.de.voidtech.gerald.service.RemindMeService; +import main.java.de.voidtech.gerald.util.ParsingUtils; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.MessageEmbed; + +@Command +public class RemindMeCommand extends AbstractCommand { + + @Autowired + private RemindMeService remindMeService; + + private static HashMap TimeMultipliers = new HashMap(); + + @EventListener(ApplicationReadyEvent.class) + private void populateTimeMultipliers() { + TimeMultipliers.put("w", Integer.valueOf(604800)); + TimeMultipliers.put("d", Integer.valueOf(86400)); + TimeMultipliers.put("h", Integer.valueOf(3600)); + TimeMultipliers.put("m", Integer.valueOf(60)); + TimeMultipliers.put("s", Integer.valueOf(1)); + } + + @Override + public void executeInternal(CommandContext context, List args) { + if (args.isEmpty()) showUserReminders(context); + else { + if (args.get(0).equals("delete")) deleteReminder(context, args); + else createReminder(context, args); + } + } + + private void createReminder(CommandContext context, List args) { + if (args.size() == 1) { + context.reply("**You need to specify a reminder message!**"); + return; + } + + String timeString = args.get(0); + String message = String.join(" ", args.subList(1, args.size())); + String timeMultiplier = timeString.substring(timeString.length() - 1, timeString.length()); + + if (!TimeMultipliers.containsKey(timeMultiplier)) { + context.reply("**You need to enter a valid time multiplier!**"); + return; + } + + String timeOnly = timeString.replaceAll(timeMultiplier, ""); + if (!ParsingUtils.isInteger(timeOnly)) { + context.reply("**You need to enter a valid time quantity! (Must be a number)**"); + return; + } + + long timeValue = Integer.parseInt(timeOnly); + if (timeValue < 1) { + context.reply("**You need to specify a higher time value! (Must be at least 1)**"); + return; + } + + long multiplicationFactor = TimeMultipliers.get(timeMultiplier).longValue(); + long time = (timeValue * multiplicationFactor) + Instant.now().getEpochSecond(); + + remindMeService.addReminder(context, message, time); + context.reply("**Reminder added!**"); + } + + private void deleteReminder(CommandContext context, List args) { + if (args.size() == 1) { + context.reply("**You need to specify a reminder ID to delete! Use the** `reminders` **command to see your reminders!**"); + return; + } + String ID = args.get(1); + if (!ParsingUtils.isInteger(ID)) { + context.reply("**Reminder ID must be a number!**"); + return; + } + boolean taskDeleted = remindMeService.deleteReminder(context.getAuthor().getId(), Long.parseLong(ID)); + context.reply(taskDeleted ? "**Reminder deleted!**" : "**No task with ID** `" + ID + "` **was found!**"); + } + + private void showUserReminders(CommandContext context) { + MessageEmbed remindersEmbed = new EmbedBuilder() + .setColor(Color.ORANGE) + .setTitle(context.getAuthor().getName() + "'s Reminders") + .setDescription(remindMeService.getRemindersList(context)) + .build(); + context.reply(remindersEmbed); + } + + @Override + public String getDescription() { + return "Need a reminder to do something in a little while? Or maybe you want to remind yourself of an event in a few weeks?" + + " This command is for you! Simply enter the time delay and a reminder message and you're set!\n\n" + + "Use w, d, h and m (months, weeks, days, hours, minutes) to set your time delay. Examples: 12d, 13m, 4w\n" + + "Please note that there should be no spaces between the quantity and time multiplier."; + } + + @Override + public String getUsage() { + return "remindme (to see your reminders)\n" + + "remindme [time delay] [reminder message]\n" + + "remindme delete [reminder ID]"; + } + + @Override + public String getName() { + return "remindme"; + } + + @Override + public CommandCategory getCommandCategory() { + return CommandCategory.UTILS; + } + + @Override + public boolean isDMCapable() { + return false; + } + + @Override + public boolean requiresArguments() { + return false; + } + + @Override + public String[] getCommandAliases() { + return new String[] {"reminder", "reminders"}; + } + + @Override + public boolean canBeDisabled() { + return true; + } + + @Override + public boolean isSlashCompatible() { + return true; + } + +} diff --git a/src/main/java/de/voidtech/gerald/entities/DelayedTask.java b/src/main/java/de/voidtech/gerald/entities/DelayedTask.java new file mode 100644 index 00000000..9ed6370a --- /dev/null +++ b/src/main/java/de/voidtech/gerald/entities/DelayedTask.java @@ -0,0 +1,74 @@ +package main.java.de.voidtech.gerald.entities; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.json.JSONObject; + +import main.java.de.voidtech.gerald.tasks.TaskType; + +@Entity +@Table(name = "delayedtask") + +public class DelayedTask { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column + private String args; + + @Column + private String type; + + @Column + private long time; + + @Column + private String guildID; + + @Column + private String userID; + + @Deprecated + //ONLY FOR HIBERNATE, DO NOT USE + DelayedTask() { + } + + public DelayedTask(TaskType type, JSONObject args, String guildID, String userID, long time) { + this.args = args.toString(); + this.type = type.getType(); + this.time = time; + this.guildID = guildID; + this.userID = userID; + } + + public String getTaskType() { + return this.type; + } + + public long getExecutionTime() { + return this.time; + } + + public JSONObject getArgs() { + return new JSONObject(this.args); + } + + public long getTaskID() { + return this.id; + } + + public String getGuildID() { + return this.guildID; + } + + public String getUserID() { + return this.userID; + } +} \ No newline at end of file diff --git a/src/main/java/de/voidtech/gerald/service/DelayedTaskService.java b/src/main/java/de/voidtech/gerald/service/DelayedTaskService.java new file mode 100644 index 00000000..d9bdd24a --- /dev/null +++ b/src/main/java/de/voidtech/gerald/service/DelayedTaskService.java @@ -0,0 +1,122 @@ +package main.java.de.voidtech.gerald.service; + +import java.time.Instant; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Level; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +import main.java.de.voidtech.gerald.entities.DelayedTask; +import main.java.de.voidtech.gerald.entities.GeraldLogger; +import main.java.de.voidtech.gerald.tasks.AbstractTask; +import main.java.de.voidtech.gerald.tasks.TaskType; +import net.dv8tion.jda.api.JDA; + +@Lazy +@Service +public class DelayedTaskService { + + @Autowired + private SessionFactory sessionFactory; + + @Autowired + private List abstractTasks; + + @Lazy + @Autowired + private JDA jda; + + //Time in milliseconds + private static final int DELAYED_TASK_TIMER_INTERVAL = 10000; + //Time in seconds + private static final int UPCOMING_TASKS_TIME = 5; + + private static final GeraldLogger LOGGER = LogService.GetLogger(DelayedTaskService.class.getSimpleName()); + + @EventListener(ApplicationReadyEvent.class) + private void startTimer() { + TimerTask requestAllowanceIncrement = new TimerTask() { + public void run() { + Thread.currentThread().setName("Task Timer"); + checkForTasks(); + } + }; + + Timer timer = new Timer(); + timer.schedule(requestAllowanceIncrement, DELAYED_TASK_TIMER_INTERVAL, DELAYED_TASK_TIMER_INTERVAL); + } + + private void checkForTasks() { + long taskTimeThreshold = Instant.now().getEpochSecond() + UPCOMING_TASKS_TIME; + List tasks = getUpcomingTasks(taskTimeThreshold); + if (tasks.isEmpty()) return; + for (DelayedTask task : tasks) { + AbstractTask taskExecutor = abstractTasks.stream() + .filter(t -> t.getTaskType().getType().equals(task.getTaskType())) + .findFirst() + .orElse(null); + if (taskExecutor == null) LOGGER.log(Level.SEVERE, "Task with unknown type '" + task.getTaskType() + "' found"); + else taskExecutor.execute(task.getArgs(), jda, task.getUserID(), task.getGuildID()); + + deleteTask(task); + } + } + + public void saveDelayedTask(DelayedTask task) { + try(Session session = sessionFactory.openSession()) + { + session.getTransaction().begin(); + session.saveOrUpdate(task); + session.getTransaction().commit(); + } + } + + public void deleteTask(DelayedTask task) { + try(Session session = sessionFactory.openSession()) { + session.getTransaction().begin(); + session.createQuery("DELETE FROM DelayedTask WHERE id = :id") + .setParameter("id", task.getTaskID()) + .executeUpdate(); + session.getTransaction().commit(); + } + } + + @SuppressWarnings("unchecked") + private List getUpcomingTasks(long taskTimeThreshold) { + try(Session session = sessionFactory.openSession()) + { + return session.createQuery("FROM DelayedTask WHERE time < :timeThreshold") + .setParameter("timeThreshold", taskTimeThreshold) + .list(); + } + } + + @SuppressWarnings("unchecked") + public List getUserTasksOfType(String userID, TaskType type) { + try(Session session = sessionFactory.openSession()) + { + return session.createQuery("FROM DelayedTask WHERE userID = :userID AND type = :type") + .setParameter("userID", userID) + .setParameter("type", type.getType()) + .list(); + } + } + + public DelayedTask getTaskByID(long id) { + try(Session session = sessionFactory.openSession()) + { + return (DelayedTask) session.createQuery("FROM DelayedTask WHERE id = :id") + .setParameter("id", id) + .uniqueResult(); + } + } + +} diff --git a/src/main/java/de/voidtech/gerald/service/RemindMeService.java b/src/main/java/de/voidtech/gerald/service/RemindMeService.java new file mode 100644 index 00000000..290a1c14 --- /dev/null +++ b/src/main/java/de/voidtech/gerald/service/RemindMeService.java @@ -0,0 +1,59 @@ +package main.java.de.voidtech.gerald.service; + +import java.util.List; + +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import main.java.de.voidtech.gerald.commands.CommandContext; +import main.java.de.voidtech.gerald.entities.DelayedTask; +import main.java.de.voidtech.gerald.tasks.TaskType; +import net.dv8tion.jda.api.entities.Guild; + +@Service +public class RemindMeService { + + @Autowired + private DelayedTaskService taskService; + + public void addReminder(CommandContext context, String message, long time) { + DelayedTask task = new DelayedTask(TaskType.REMIND_ME, + new JSONObject().put("channelID", context.getChannel().getId()).put("message", message), + context.getGuild().getId(), + context.getAuthor().getId(), + time); + taskService.saveDelayedTask(task); + } + + public String getRemindersList(CommandContext context) { + List tasks = taskService.getUserTasksOfType(context.getAuthor().getId(), TaskType.REMIND_ME); + + String list = ""; + + for (DelayedTask task : tasks) { + Guild guild = context.getJDA().getGuildById(task.getGuildID()); + if (guild == null) { + taskService.deleteTask(task); + } else { + list += "`" + task.getTaskID() + "` **" + guild.getName() + "** - - "; + list += formatMessage(task.getArgs().getString("message")); + list += "\n"; + } + } + return list == "" ? "**No reminders!**" : list; + } + + private String formatMessage(String msg) { + return msg.length() > 20 ? msg.substring(0, 20) + "..." : msg; + } + + public boolean deleteReminder(String userID, long id) { + DelayedTask task = taskService.getTaskByID(id); + if (task == null) return false; + if (!task.getUserID().equals(userID)) return false; + taskService.deleteTask(task); + return true; + } + +} diff --git a/src/main/java/de/voidtech/gerald/tasks/AbstractTask.java b/src/main/java/de/voidtech/gerald/tasks/AbstractTask.java new file mode 100644 index 00000000..02ad2c34 --- /dev/null +++ b/src/main/java/de/voidtech/gerald/tasks/AbstractTask.java @@ -0,0 +1,21 @@ +package main.java.de.voidtech.gerald.tasks; + +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; + +import main.java.de.voidtech.gerald.service.ThreadManager; +import net.dv8tion.jda.api.JDA; + +public abstract class AbstractTask { + + @Autowired + private ThreadManager threadManager; + + public void execute(JSONObject args, JDA jda, String userID, String guildID) { + Runnable taskRunnable = () -> executeInternal(args, jda, userID, guildID); + threadManager.getThreadByName("T-Task").execute(taskRunnable); + } + + public abstract TaskType getTaskType(); + public abstract void executeInternal(JSONObject args, JDA jda, String userID, String guildID); +} diff --git a/src/main/java/de/voidtech/gerald/tasks/RemindMeTask.java b/src/main/java/de/voidtech/gerald/tasks/RemindMeTask.java new file mode 100644 index 00000000..8c7e387e --- /dev/null +++ b/src/main/java/de/voidtech/gerald/tasks/RemindMeTask.java @@ -0,0 +1,31 @@ +package main.java.de.voidtech.gerald.tasks; + +import org.json.JSONObject; + +import main.java.de.voidtech.gerald.annotations.Task; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.TextChannel; + +@Task +public class RemindMeTask extends AbstractTask { + + @Override + public void executeInternal(JSONObject args, JDA jda, String userID, String guildID) { + String channelID = args.getString("channelID"); + String message = "**Reminder for** <@" + userID + "> - " + args.getString("message"); + + Guild guild = jda.getGuildById(guildID); + if (guild == null) return; + TextChannel channel = guild.getTextChannelById(channelID); + if (channel == null) return; + + channel.sendMessage(message).queue(); + } + + @Override + public TaskType getTaskType() { + return TaskType.REMIND_ME; + } + +} diff --git a/src/main/java/de/voidtech/gerald/tasks/TaskType.java b/src/main/java/de/voidtech/gerald/tasks/TaskType.java new file mode 100644 index 00000000..ea3e228c --- /dev/null +++ b/src/main/java/de/voidtech/gerald/tasks/TaskType.java @@ -0,0 +1,15 @@ +package main.java.de.voidtech.gerald.tasks; + +public enum TaskType { + REMIND_ME("remind_me"); + + private final String type; + + TaskType(String type) { + this.type = type; + } + + public String getType() { + return type; + } +}