Implement scheduled tasks

This commit is contained in:
anthorob committed 2025-10-22 20:11:03 +00:00
1 parent 78a149b546
commit 78002d9299
42 files changed
+403 -283

No files matched your search

+1
View File
@@ -4,6 +4,7 @@ NETNEUTRALITY_BOT_ENV=dev
NETNEUTRALITY_BOT_TOKEN=
NETNEUTRALITY_BOT_PREFIX=$
NETNEUTRALITY_BOT_OWNER=
NETNEUTRALITY_BOT_INVITE=
NETNEUTRALITY_API_PORT=9000
MYSQL_HOST=127.0.0.1
+1
View File
@@ -2,6 +2,7 @@ NETNEUTRALITY_BOT_ENV=dev
NETNEUTRALITY_BOT_TOKEN=
NETNEUTRALITY_BOT_PREFIX=
NETNEUTRALITY_BOT_OWNER=
NETNEUTRALITY_BOT_INVITE=
NETNEUTRALITY_API_PORT=9000
MYSQL_HOST=127.0.0.1
-64
View File
@@ -1,64 +0,0 @@
services:
db:
image: mariadb:lts@sha256:4b1e7958f820681b1b93b9cf9ea05a76e0785da12699a1fb1d05e0eb2137e56b
container_name: db
restart: unless-stopped
volumes:
- netneutrality-db:/var/lib/mysql
- ./sql:/docker-entrypoint-initdb.d
environment:
MYSQL_USER: 'java'
MYSQL_PASSWORD: 'password'
MYSQL_DATABASE: 'netneutrality'
MYSQL_ROOT_PASSWORD: 'password'
MARIADB_AUTO_UPGRADE: '1'
ports:
- "3306"
networks:
- internal
bot:
container_name: bot
build: ./
restart: unless-stopped
depends_on:
- db
volumes:
- ./logs:/app/logs
environment:
MYSQL_HOST: 'db'
MYSQL_USER: 'java'
MYSQL_PASSWORD: 'password'
MYSQL_DATABASE: 'netneutrality'
ports:
- "9000:9000"
networks:
- internal
webapp:
image: bot-webapp:dev
build: ../netneutrality-webapp
restart: always
depends_on:
- db
- bot
volumes:
- ./logs:/app/logs
environment:
DB_CONNECTION: 'mysql'
DB_HOST: 'db'
DB_PORT: '3306'
DB_DATABASE: 'netneutrality'
DB_USERNAME: 'java'
DB_PASSWORD: 'password'
BOT_URL: 'http://bot:9000'
ports:
- '8080:80'
networks:
- internal
volumes:
netneutrality-db:
networks:
internal:
+1 -1
View File
@@ -1,6 +1,6 @@
services:
netneutrality-db:
image: mariadb:lts@sha256:4b1e7958f820681b1b93b9cf9ea05a76e0785da12699a1fb1d05e0eb2137e56b
image: mariadb:11
container_name: db
restart: unless-stopped
networks:
-5
View File
@@ -259,11 +259,6 @@
<artifactId>commons-lang3</artifactId>
<version>3.17.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.18.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
+9 -1
View File
@@ -9,7 +9,15 @@
{
"groupName": "hibernate orm",
"matchPackageNames": [
"org.hibernate.orm:**"
"org.hibernate.orm:**",
"com.mysql:**"
]
},
{
"groupName": "apache commons",
"matchPackageNames": [
"commons:**",
"org.apache.commons:**"
]
}
]
@@ -12,11 +12,11 @@ import me.bobert.netneutrality.core.command.CommandManager;
import me.bobert.netneutrality.core.db.DatabaseMigration;
import me.bobert.netneutrality.core.db.SessionManager;
import me.bobert.netneutrality.core.tasks.TaskManager;
import me.bobert.netneutrality.core.thread.ScheduledTaskManager;
import me.bobert.netneutrality.core.thread.ThreadManager;
import me.bobert.netneutrality.hooks.EventsHandler;
import me.bobert.netneutrality.http.server.WebApi;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.thread.ShutdownThread;
import me.bobert.netneutrality.utils.Settings;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
@@ -79,7 +79,7 @@ public class NetNeutrality {
new WebApi(Integer.parseInt(Settings.getInstance().get("NETNEUTRALITY_API_PORT", "9000")));
jda.getPresence().setActivity(Activity.watching("you"));
Runtime.getRuntime().addShutdownHook(new ShutdownThread(this));
Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));
}
public static NetNeutrality getInstance() {
@@ -108,6 +108,7 @@ public class NetNeutrality {
public void shutdown() {
System.out.println("[Net Neutrality] Shutting down...");
ScheduledTaskManager.cancelAll();
MusicManager.disconnectPlayers();
ThreadManager.shutdownExecutor();
disconnect();
@@ -13,7 +13,7 @@ import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
import me.bobert.netneutrality.core.command.impl.AbstractCommand;
import me.bobert.netneutrality.core.command.options.CommandOption;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.tasks.BuiltInTask;
import me.bobert.netneutrality.core.tasks.Task;
import me.bobert.netneutrality.tasks.help.BotInfo;
import me.bobert.netneutrality.tasks.help.CommandHelp;
import me.bobert.netneutrality.tasks.help.ReportBug;
@@ -65,9 +65,9 @@ public class HelpCommand extends AbstractCommand {
public CommandExecutionResult execute(CommandContext context) {
String command = context.getOption("command", null, CommandOption::getAsString);
if (command == null) {
Button commands = BuiltInTask.createButton(CommandHelp.class);
Button info = BuiltInTask.createButton(BotInfo.class);
Button reportbug = BuiltInTask.createButton(ReportBug.class);
Button commands = Task.createButton(CommandHelp.class);
Button info = Task.createButton(BotInfo.class);
Button reportbug = Task.createButton(ReportBug.class);
ActionRow row = ActionRow.of(commands, info, reportbug);
context.replyEphemeral("Thanks for using the Net Neutrality Bot! What do you need help with?", List.of(row));
return CommandExecutionResult.none();
@@ -5,6 +5,7 @@ package me.bobert.netneutrality.core.command;
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.utils.ReflectionHelper;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.interactions.InteractionContextType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
@@ -12,9 +13,7 @@ import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
import net.dv8tion.jda.api.requests.restaction.CommandListUpdateAction;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.reflections.Reflections;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -39,34 +38,27 @@ public class CommandManager {
}
public static void initCommands() {
Set<Class<? extends Command>> classes = new Reflections("me.bobert.netneutrality.command").getSubTypesOf(Command.class);
for (Class<? extends Command> s : classes) {
try {
if (Modifier.isAbstract(s.getModifiers())) {
continue;
}
Command command = s.getConstructor().newInstance();
if (!command.isEnabled()) {
continue;
}
if (command.getCommandName() == null) {
throw new IllegalArgumentException("Command name is null");
}
if (command.getDescription() == null && command.getType() == Command.Type.SLASH) {
throw new IllegalArgumentException("Command description is null");
}
CommandContainer container = new CommandContainer(command);
if (!commands.containsKey(command.getCommandName())) {
commands.put(command.getCommandName(), container);
}
} catch (Exception e) {
logger.error("Failed to load command {}: {}", s.getSimpleName(), e.getMessage());
ReflectionHelper.loadCommands(Command.class, command -> {
if (!command.isEnabled()) {
logger.info("Skipping command {} as it is disabled", command.getClass().getName());
return;
}
}
if (command.getCommandName() == null) {
logger.error("Failed to load command {}: Command name is null", command.getClass().getName());
return;
}
if (command.getDescription() == null && command.getType() == Command.Type.SLASH) {
logger.error("Failed to load command {}: Command description is null", command.getClass().getName());
return;
}
CommandContainer container = new CommandContainer(command);
if (!commands.containsKey(command.getCommandName())) {
commands.put(command.getCommandName(), container);
}
});
}
public static void registerSlashCommands(JDA jda) {
@@ -65,7 +65,7 @@ public class CommandProcessor {
}
try {
logger.info("Context ID: {}", context.getId());
logger.debug("Context ID: {}", context.getId());
logger.info("Executing command {} on guild {}", command.getCommandName(), context.getGuild() == null ? "PrivateMessage" : context.getGuild().getId());
configureSentry(command);
@@ -151,6 +151,10 @@ public interface Context {
replyEphemeralWithLimit(message);
}
default void replyEphemeral(String message, LayoutComponent component) {
replyEphemeral(message, List.of(component));
}
default void replyEphemeral(String message, List<LayoutComponent> components) {
replyEphemeral(MessageCreateData.fromContent(message), components);
}
@@ -5,7 +5,7 @@ package me.bobert.netneutrality.core.tasks;
* https://discord.gg/qUa6zNFZsd
*/
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import me.bobert.netneutrality.core.context.TaskContext;
/**
* Represents a task that has a known ID and is loaded at runtime.
@@ -13,13 +13,5 @@ import net.dv8tion.jda.api.interactions.components.buttons.Button;
*/
public interface BuiltInTask extends Task {
static Button createButton(Class<? extends BuiltInTask> clazz) {
BuiltInTask builtInTask = TaskManager.getInstance().findBuiltInTaskByClass(clazz);
if (builtInTask == null) {
throw new IllegalArgumentException("Task class " + clazz.getName() + " was not registered by the task manager.");
}
return builtInTask.createButton();
}
void executeTask(TaskContext context);
}
@@ -0,0 +1,64 @@
package me.bobert.netneutrality.core.tasks;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import java.util.concurrent.TimeUnit;
public interface ScheduledTask {
class TimeFrame {
private final int interval;
private final TimeUnit unit;
private TimeFrame(int interval, TimeUnit unit) {
this.interval = interval;
this.unit = unit;
}
public static TimeFrame minutely() {
return new TimeFrame(1, TimeUnit.MINUTES);
}
public static TimeFrame minutely(int interval) {
return new TimeFrame(interval, TimeUnit.MINUTES);
}
public static TimeFrame hourly() {
return new TimeFrame(1, TimeUnit.HOURS);
}
public static TimeFrame hourly(int interval) {
return new TimeFrame(interval, TimeUnit.HOURS);
}
public static TimeFrame daily() {
return new TimeFrame(1, TimeUnit.DAYS);
}
public static TimeFrame daily(int interval) {
return new TimeFrame(interval, TimeUnit.DAYS);
}
public static TimeFrame custom(int interval, TimeUnit unit) {
return new TimeFrame(interval, unit);
}
public int getInterval() {
return interval;
}
public TimeUnit getUnit() {
return unit;
}
}
String getId();
TimeFrame getTimeFrame();
void run();
}
@@ -6,7 +6,7 @@ package me.bobert.netneutrality.core.tasks;
* https://discord.gg/qUa6zNFZsd
*/
public interface IStepTask {
public interface StepTask {
/**
* Defines the type of input expected for a step.
@@ -7,6 +7,7 @@ package me.bobert.netneutrality.core.tasks;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.trait.WithButton;
import me.bobert.netneutrality.core.tasks.trait.WithChildren;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
@@ -64,8 +65,6 @@ public interface Task {
OffsetDateTime getCreationTime();
Button createButton();
/**
* <p>This method is used to reply to the interaction that triggered this task.</p>
* <p>If the task is implementing {@link WithChildren}, the child tasks must be instanciated in this method.</p>
@@ -80,4 +79,14 @@ public interface Task {
Lifecycle execute(TaskContext context);
void insert(CommandContext context);
static Button createButton(Class<? extends WithButton> clazz) {
WithButton withButton = TaskManager.getInstance().findButtonTaskByClass(clazz);
if (withButton == null) {
throw new IllegalArgumentException("Task " + clazz.getName() + " was not registered by the task manager.");
}
return withButton.createButton();
}
}
@@ -11,28 +11,24 @@ import me.bobert.netneutrality.core.context.InteractionContext;
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.trait.Deletable;
import me.bobert.netneutrality.core.tasks.trait.NotAcknowledgeable;
import me.bobert.netneutrality.core.tasks.trait.WithButton;
import me.bobert.netneutrality.core.tasks.trait.WithChildren;
import me.bobert.netneutrality.core.tasks.trait.WithParent;
import me.bobert.netneutrality.core.thread.ThreadManager;
import me.bobert.netneutrality.core.thread.ScheduledTaskManager;
import me.bobert.netneutrality.db.service.TaskService;
import me.bobert.netneutrality.exceptions.GracefulContextError;
import me.bobert.netneutrality.exceptions.TaskExecutionException;
import me.bobert.netneutrality.thread.CleanupTaskThread;
import me.bobert.netneutrality.utils.ReflectionHelper;
import net.dv8tion.jda.api.interactions.InteractionHook;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.reflections.Reflections;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
@@ -63,12 +59,11 @@ public class TaskManager {
public void init() {
loadBuiltInTasks();
loadScheduledTasks();
taskService.loadTasks();
ThreadManager.getScheduledExecutor().scheduleWithFixedDelay(new CleanupTaskThread(), 1, 5, TimeUnit.MINUTES);
}
protected TaskService getTaskService() {
public TaskService getTaskService() {
return taskService;
}
@@ -101,7 +96,7 @@ public class TaskManager {
taskService.persistTask(persistableTask);
}
if (task instanceof IStepTask) {
if (task instanceof StepTask) {
pendingTasks.add(task);
} else {
tasks.put(id, task);
@@ -176,7 +171,7 @@ public class TaskManager {
}
}
logger.debug("Executing task: {}", taskId);
logger.info("Executing task: {}", taskId);
Task.Lifecycle status = task.execute(context);
handleTaskLifecycle(task, status);
@@ -205,7 +200,7 @@ public class TaskManager {
public <E extends Task> Optional<E> findTaskByPartialId(Class<E> clazz, String partialId) {
List<E> tasks = findTasksByPartialId(clazz, partialId);
return Optional.ofNullable(tasks.isEmpty() ? null : tasks.get(0));
return Optional.ofNullable(tasks.isEmpty() ? null : tasks.getFirst());
}
public <E extends Task> List<E> findTasksByPartialId(Class<E> clazz, String partialId) {
@@ -216,10 +211,10 @@ public class TaskManager {
.collect(Collectors.toList());
}
public BuiltInTask findBuiltInTaskByClass(Class<? extends BuiltInTask> clazz) {
public WithButton findButtonTaskByClass(Class<? extends WithButton> clazz) {
return tasks.values().stream()
.filter(clazz::isInstance)
.map(task -> (BuiltInTask) task)
.map(task -> (WithButton) task)
.findFirst()
.orElse(null);
}
@@ -237,22 +232,27 @@ public class TaskManager {
private void loadBuiltInTasks() {
logger.info("Loading built-in tasks...");
Set<Class<? extends BuiltInTask>> classes = new Reflections("me.bobert.netneutrality.tasks").getSubTypesOf(BuiltInTask.class);
for (Class<? extends BuiltInTask> s : classes) {
try {
if (Modifier.isAbstract(s.getModifiers())) {
continue;
}
BuiltInTask task = s.getConstructor().newInstance();
logger.info("Loaded task: {}", task);
tasks.put(task.getId(), task);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
logger.error("Failed to instantiate built-in task", e);
ReflectionHelper.loadTasks(BuiltInTask.class, builtInTask -> {
if (builtInTask.isInsertable()) {
logger.info("Loaded task: {}", builtInTask.getClass().getName());
tasks.put(builtInTask.getId(), builtInTask);
}
}
});
}
private void loadScheduledTasks() {
logger.info("Loading scheduled tasks...");
ReflectionHelper.loadTasks(ScheduledTask.class, scheduledTask -> {
String taskId = scheduledTask.getId();
if (taskId == null || taskId.trim().isEmpty()) {
logger.error("Cannot load scheduled task {}: task ID is null or empty", scheduledTask.getClass().getName());
return;
}
ScheduledTaskManager.schedule(scheduledTask);
});
}
private void deleteInteraction(Task task) {
@@ -1,4 +1,4 @@
package me.bobert.netneutrality.core.tasks;
package me.bobert.netneutrality.core.tasks.impl;
/*
* Copyright (c) anthorob
*
@@ -7,6 +7,7 @@ package me.bobert.netneutrality.core.tasks;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.BuiltInTask;
public abstract class AbstractBuiltInTask extends AbstractTask implements BuiltInTask {
@@ -35,6 +36,4 @@ public abstract class AbstractBuiltInTask extends AbstractTask implements BuiltI
executeTask(context);
return Lifecycle.BUILTIN;
}
public abstract void executeTask(TaskContext context);
}
@@ -1,4 +1,4 @@
package me.bobert.netneutrality.core.tasks;
package me.bobert.netneutrality.core.tasks.impl;
/*
* Copyright (c) anthorob
*
@@ -6,6 +6,8 @@ package me.bobert.netneutrality.core.tasks;
*/
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.tasks.PersistableTask;
import me.bobert.netneutrality.core.tasks.TaskManager;
import org.json.JSONObject;
public abstract class AbstractPersistableTask extends AbstractTask implements PersistableTask {
@@ -9,13 +9,11 @@ package me.bobert.netneutrality.core.tasks.impl;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.context.MessageContext;
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.AbstractTask;
import me.bobert.netneutrality.core.tasks.IStepTask;
import me.bobert.netneutrality.core.tasks.StepTask;
import me.bobert.netneutrality.core.tasks.TaskManager;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.fellbaum.jemoji.Emoji;
import net.fellbaum.jemoji.EmojiManager;
@@ -27,7 +25,7 @@ import java.util.UUID;
* Represents a task that consists of multiple sequential steps. The task starts with an initial message (question)
* and expects a user response. Each step can lead to another step, forming a workflow.
*/
public abstract class StepTask extends AbstractTask implements IStepTask {
public abstract class AbstractStepTask extends AbstractTask implements StepTask {
private final Object stepLock = new Object();
private CommandContext commandContext;
private Guild guild;
@@ -110,11 +108,6 @@ public abstract class StepTask extends AbstractTask implements IStepTask {
return getCreationTime().plusMinutes(60).isBefore(OffsetDateTime.now());
}
@Override
public Button createButton() {
return null;
}
/**
* Validates the user input against the expected step type.
*
@@ -1,4 +1,4 @@
package me.bobert.netneutrality.core.tasks;
package me.bobert.netneutrality.core.tasks.impl;
/*
* Copyright (c) anthorob
*
@@ -6,6 +6,8 @@ package me.bobert.netneutrality.core.tasks;
*/
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.tasks.Task;
import me.bobert.netneutrality.core.tasks.TaskManager;
import java.time.OffsetDateTime;
import java.util.Objects;
@@ -5,49 +5,40 @@ package me.bobert.netneutrality.core.tasks.impl;
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.tasks.GenericRandomIdTask;
import me.bobert.netneutrality.core.tasks.Task;
import me.bobert.netneutrality.core.tasks.trait.WithChildren;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import me.bobert.netneutrality.core.tasks.trait.WithButton;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
import java.time.OffsetDateTime;
import java.util.List;
public abstract class ButtonTask extends GenericRandomIdTask {
public abstract class ButtonTask extends GenericRandomIdTask implements WithButton {
private final ButtonStyle buttonStyle;
private final String buttonLabel;
private Emoji emoji;
public ButtonTask(ButtonStyle buttonStyle, String buttonLabel) {
this.buttonStyle = buttonStyle;
this.buttonLabel = buttonLabel;
}
public Task setEmoji(Emoji emoji) {
this.emoji = emoji;
return this;
}
@Override
public Button createButton() {
if (buttonStyle == null || buttonLabel == null) {
return null;
}
return Button.of(buttonStyle, getId(), buttonLabel);
return Button.of(buttonStyle, getId(), buttonLabel, emoji);
}
@Override
public boolean isExpired() {
return getCreationTime().plusMinutes(15).isBefore(OffsetDateTime.now());
}
public ActionRow createActionRow(List<Task> tasks) {
ActionRow row = ActionRow.of(tasks.stream().map(Task::createButton).toList());
if (this instanceof WithChildren parent) {
parent.addChild(tasks);
}
return row;
}
public ActionRow createActionRow(Task... tasks) {
return createActionRow(List.of(tasks));
}
}
@@ -6,8 +6,6 @@ package me.bobert.netneutrality.core.tasks.impl;
*/
import me.bobert.netneutrality.context.tasks.EmojiTaskContext;
import me.bobert.netneutrality.core.tasks.AbstractPersistableTask;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -54,9 +52,4 @@ public abstract class EmojiTask extends AbstractPersistableTask {
public String getId() {
return String.format(EmojiTaskContext.ID, getGuildId(), getMessageId());
}
@Override
public Button createButton() {
return null;
}
}
@@ -1,4 +1,4 @@
package me.bobert.netneutrality.core.tasks;
package me.bobert.netneutrality.core.tasks.impl;
/*
* Copyright (c) anthorob
@@ -6,8 +6,6 @@ package me.bobert.netneutrality.core.tasks;
* https://discord.gg/qUa6zNFZsd
*/
import net.dv8tion.jda.api.interactions.components.buttons.Button;
public abstract class GenericCustomIdTask extends AbstractTask {
private final String customId;
@@ -19,9 +17,4 @@ public abstract class GenericCustomIdTask extends AbstractTask {
public String getId() {
return customId;
}
@Override
public Button createButton() {
return null;
}
}
@@ -1,4 +1,4 @@
package me.bobert.netneutrality.core.tasks;
package me.bobert.netneutrality.core.tasks.impl;
/*
* Copyright (c) anthorob
@@ -6,7 +6,6 @@ package me.bobert.netneutrality.core.tasks.impl;
*/
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.GenericRandomIdTask;
import net.dv8tion.jda.api.interactions.modals.ModalMapping;
import java.time.OffsetDateTime;
@@ -0,0 +1,31 @@
package me.bobert.netneutrality.core.tasks.trait;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.tasks.Task;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import java.util.List;
public interface WithButton {
Button createButton();
default ActionRow createActionRow(List<Task> tasks) {
ActionRow row = ActionRow.of(tasks.stream().filter(task -> task instanceof WithButton).map(task -> (WithButton) task).map(WithButton::createButton).toList());
if (this instanceof WithChildren parent) {
parent.addChild(tasks);
}
return row;
}
default ActionRow createActionRow(Task... tasks) {
return createActionRow(List.of(tasks));
}
}
@@ -0,0 +1,64 @@
package me.bobert.netneutrality.core.thread;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.LogHandler;
import me.bobert.netneutrality.core.tasks.ScheduledTask;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
public class ScheduledTaskManager {
private static final Logger logger = LogManager.getLogger(ScheduledTaskManager.class);
private static final ScheduledExecutorService executorService = ThreadManager.getScheduledExecutor();
private static final Map<String, ScheduledFuture<?>> scheduledTasks = new ConcurrentHashMap<>();
private ScheduledTaskManager() {}
public static void schedule(ScheduledTask task) {
String taskId = task.getId();
if (scheduledTasks.containsKey(taskId)) {
logger.warn("Task {} is already scheduled.", taskId);
return;
}
ScheduledTask.TimeFrame timeFrame = task.getTimeFrame();
Runnable runnable = () -> {
logger.info("Executing scheduled task: {}", taskId);
try {
task.run();
} catch (Exception e) {
logger.error("An exception was thrown while executing scheduled task {}", taskId);
LogHandler.log(e);
}
};
ScheduledFuture<?> future = executorService.scheduleWithFixedDelay(
runnable,
1,
timeFrame.getInterval(),
timeFrame.getUnit()
);
scheduledTasks.put(taskId, future);
logger.info("Successfully scheduled task: {} with interval {} {}",
taskId, timeFrame.getInterval(), timeFrame.getUnit());
}
public static void cancelAll() {
logger.info("Cancelling all {} scheduled tasks", scheduledTasks.size());
scheduledTasks.forEach((id, future) -> future.cancel(false));
scheduledTasks.clear();
}
}
@@ -15,12 +15,9 @@ import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import dev.lavalink.youtube.YoutubeAudioSourceManager;
import me.bobert.netneutrality.core.command.CommandExecutionResult;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.thread.ThreadManager;
import me.bobert.netneutrality.db.GSettings;
import me.bobert.netneutrality.db.service.GuildService;
import me.bobert.netneutrality.music.source.UdioAudioSourceManager;
import me.bobert.netneutrality.thread.MusicManagerCleanupThread;
import me.bobert.netneutrality.thread.MusicPlayerAutoDisconnectThread;
import me.bobert.netneutrality.utils.MusicUtils;
import me.bobert.netneutrality.utils.Settings;
import net.dv8tion.jda.api.entities.Guild;
@@ -31,7 +28,6 @@ import org.apache.logging.log4j.Logger;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class MusicManager {
@@ -72,9 +68,6 @@ public class MusicManager {
String po = Settings.getInstance().get("NETNEUTRALITY_PO_TOKEN");
String visitor = Settings.getInstance().get("NETNEUTRALITY_VISITOR_DATA");
dev.lavalink.youtube.clients.Web.setPoTokenAndVisitorData(po, visitor);
ThreadManager.getScheduledExecutor().scheduleWithFixedDelay(new MusicPlayerAutoDisconnectThread(), 15, 15, TimeUnit.MINUTES);
ThreadManager.getScheduledExecutor().scheduleWithFixedDelay(new MusicManagerCleanupThread(), 1, 1, TimeUnit.HOURS);
}
public static void disconnectPlayers() {
@@ -6,10 +6,12 @@ package me.bobert.netneutrality.tasks.help;
*/
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.AbstractBuiltInTask;
import me.bobert.netneutrality.core.tasks.impl.AbstractBuiltInTask;
import me.bobert.netneutrality.core.tasks.trait.WithButton;
import me.bobert.netneutrality.utils.Settings;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
public class BotInfo extends AbstractBuiltInTask {
public class BotInfo extends AbstractBuiltInTask implements WithButton {
@Override
public String getId() {
@@ -23,7 +25,7 @@ public class BotInfo extends AbstractBuiltInTask {
@Override
public void executeTask(TaskContext context) {
context.sendMessageInPrivate("For information about the bot and to follow the development, " +
"you can join the discord here: https://discord.gg/udVJwTgV");
context.replyEphemeral("For information about the bot and to follow the development, " +
"you can join the discord here: " + Settings.getDiscordInvite());
}
}
@@ -6,12 +6,13 @@ package me.bobert.netneutrality.tasks.help;
*/
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.AbstractBuiltInTask;
import me.bobert.netneutrality.core.tasks.impl.AbstractBuiltInTask;
import me.bobert.netneutrality.core.tasks.trait.WithButton;
import me.bobert.netneutrality.utils.MessageFunction;
import me.bobert.netneutrality.utils.Settings;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
public class CommandHelp extends AbstractBuiltInTask {
public class CommandHelp extends AbstractBuiltInTask implements WithButton {
@Override
public String getId() {
@@ -6,10 +6,12 @@ package me.bobert.netneutrality.tasks.help;
*/
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.AbstractBuiltInTask;
import me.bobert.netneutrality.core.tasks.impl.AbstractBuiltInTask;
import me.bobert.netneutrality.core.tasks.trait.WithButton;
import me.bobert.netneutrality.utils.Settings;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
public class ReportBug extends AbstractBuiltInTask {
public class ReportBug extends AbstractBuiltInTask implements WithButton {
@Override
public String getId() {
@@ -23,7 +25,7 @@ public class ReportBug extends AbstractBuiltInTask {
@Override
public void executeTask(TaskContext context) {
context.sendMessageInPrivate("To report a bug with the bot, you can either use the /report-bug command " +
"or join the discord here: https://discord.gg/udVJwTgV");
context.replyEphemeral("To report a bug with the bot, you can either use the /report-bug command " +
"or join the discord here: " + Settings.getDiscordInvite());
}
}
@@ -109,15 +109,12 @@ public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deleta
}
return Lifecycle.KEEP;
});
}).setEmoji(Emoji.fromFormatted("U+1F60E"));
Task cancel = CancelButton.create(this, cancelText, context -> Lifecycle.REMOVE_SELF_AND_CHILDREN);
addChild(accept, cancel);
Task cancel = CancelButton.create(this, cancelText, context -> Lifecycle.REMOVE_SELF_AND_CHILDREN)
.setEmoji(Emoji.fromFormatted("U+1F92E"));
return ActionRow.of(
accept.createButton().withEmoji(Emoji.fromFormatted("U+1F60E")),
cancel.createButton().withEmoji(Emoji.fromFormatted("U+1F92E"))
);
return createActionRow(accept, cancel);
}
@Override
@@ -6,11 +6,11 @@ package me.bobert.netneutrality.tasks.reaction;
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.tasks.impl.StepTask;
import me.bobert.netneutrality.core.tasks.impl.AbstractStepTask;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.Role;
public class EmojiRoleFirstStep extends StepTask {
public class EmojiRoleFirstStep extends AbstractStepTask {
private final Message target;
public EmojiRoleFirstStep(Message target) {
@@ -74,7 +74,7 @@ public class EmojiRoleModify extends ButtonTask implements WithChildren {
});
ActionRow row = createActionRow(addEmojiTask, removeEmojiTask, deleteMappingTask);
context.replyEphemeral("Select what you want to do with this message", List.of(row));
context.replyEphemeral("Select what you want to do with this message", row);
}
@Override
@@ -6,14 +6,14 @@ package me.bobert.netneutrality.tasks.reaction;
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.tasks.impl.StepTask;
import me.bobert.netneutrality.core.tasks.impl.AbstractStepTask;
import me.bobert.netneutrality.tasks.emoji.EmojiReactionRoleTask;
import net.dv8tion.jda.api.entities.Message;
import java.util.Set;
import java.util.StringJoiner;
public class EmojiRoleRemoveStep extends StepTask {
public class EmojiRoleRemoveStep extends AbstractStepTask {
private final Message target;
private final EmojiReactionRoleTask task;
@@ -22,6 +22,11 @@ public class EmojiRoleRemoveStep extends StepTask {
this.task = task;
}
@Override
public String getName() {
return "emoji-role-remove";
}
@Override
public StepType getStepType() {
return StepType.EMOJI;
@@ -54,9 +59,4 @@ public class EmojiRoleRemoveStep extends StepTask {
task.removeEmoji(emoji);
return StepResult.success("Removed emoji successfully!");
}
@Override
public String getName() {
return "emoji-role-remove";
}
}
@@ -8,14 +8,14 @@ package me.bobert.netneutrality.tasks.reaction;
import me.bobert.netneutrality.core.tasks.Task;
import me.bobert.netneutrality.core.tasks.TaskManager;
import me.bobert.netneutrality.core.tasks.impl.StepTask;
import me.bobert.netneutrality.core.tasks.impl.AbstractStepTask;
import me.bobert.netneutrality.tasks.emoji.EmojiReactionRoleTask;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.Role;
import java.util.Map;
public class EmojiRoleSecondStep extends StepTask {
public class EmojiRoleSecondStep extends AbstractStepTask {
private final Message target;
private final Role role;
@@ -1,27 +1,35 @@
package me.bobert.netneutrality.thread;
package me.bobert.netneutrality.tasks.scheduled;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.tasks.ScheduledTask;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.music.TrackScheduler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class MusicManagerCleanupThread implements Runnable {
private static final Logger logger = LogManager.getLogger(MusicManagerCleanupThread.class);
public class MusicManagerCleanup implements ScheduledTask {
private static final Logger logger = LogManager.getLogger(MusicManagerCleanup.class);
@Override
public String getId() {
return "MusicManagerCleanup";
}
@Override
public TimeFrame getTimeFrame() {
return TimeFrame.hourly();
}
@Override
public void run() {
try {
for (MusicManager manager : MusicManager.getManagers()) {
processManager(manager);
}
} catch (InterruptedException e) {
logger.error("Interrupted while running MusicManagerCleanupThread", e);
}
@@ -1,10 +1,12 @@
package me.bobert.netneutrality.thread;
package me.bobert.netneutrality.tasks.scheduled;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.tasks.ScheduledTask;
import me.bobert.netneutrality.music.MusicManager;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel;
@@ -13,17 +15,25 @@ import org.apache.logging.log4j.Logger;
import java.util.List;
public class MusicPlayerAutoDisconnectThread implements Runnable {
private static final Logger logger = LogManager.getLogger(MusicPlayerAutoDisconnectThread.class);
public class MusicPlayerAutoDisconnect implements ScheduledTask {
private static final Logger logger = LogManager.getLogger(MusicPlayerAutoDisconnect.class);
@Override
public String getId() {
return "MusicPlayerAutoDisconnect";
}
@Override
public TimeFrame getTimeFrame() {
return TimeFrame.minutely(15);
}
@Override
public void run() {
try {
for (MusicManager manager : MusicManager.getManagers()) {
processManager(manager);
}
} catch (InterruptedException e) {
logger.error("Interrupted while running MusicPlayerAutoDisconnectThread", e);
}
@@ -1,4 +1,4 @@
package me.bobert.netneutrality.thread;/*
package me.bobert.netneutrality.tasks.scheduled;/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
@@ -6,6 +6,7 @@ package me.bobert.netneutrality.thread;/*
import me.bobert.netneutrality.core.tasks.BuiltInTask;
import me.bobert.netneutrality.core.tasks.PersistableTask;
import me.bobert.netneutrality.core.tasks.ScheduledTask;
import me.bobert.netneutrality.core.tasks.Task;
import me.bobert.netneutrality.core.tasks.TaskManager;
import org.apache.logging.log4j.LogManager;
@@ -13,8 +14,18 @@ import org.apache.logging.log4j.Logger;
import java.util.List;
public class CleanupTaskThread implements Runnable {
private static final Logger logger = LogManager.getLogger(CleanupTaskThread.class);
public class TaskCleanup implements ScheduledTask {
private static final Logger logger = LogManager.getLogger(TaskCleanup.class);
@Override
public String getId() {
return "TaskCleanup";
}
@Override
public TimeFrame getTimeFrame() {
return TimeFrame.minutely(5);
}
@Override
public void run() {
@@ -1,33 +0,0 @@
package me.bobert.netneutrality.thread;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.NetNeutrality;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configurator;
public class ShutdownThread extends Thread {
private final NetNeutrality main;
public ShutdownThread(NetNeutrality main) {
this.main = main;
}
@Override
public void run() {
if (LogManager.getContext() instanceof LoggerContext) {
System.out.println("Shutting down log4j2");
Configurator.shutdown((LoggerContext) LogManager.getContext());
}
if (main.getJDA() != null) {
main.shutdown();
}
}
}
@@ -0,0 +1,55 @@
package me.bobert.netneutrality.utils;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.reflections.Reflections;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class ReflectionHelper {
private static final Logger logger = LogManager.getLogger(ReflectionHelper.class);
private static final BiConsumer<Class<?>, Exception> DEFAULT_FAILURE = (aClass, e) -> logger.error("Failed to load class: {}", aClass.getName(), e);
public static <E> void loadClassesFromPackage(String packageName, Class<E> clazz, Consumer<E> onInstanciated, BiConsumer<Class<?>, Exception> onFailed) {
Set<Class<? extends E>> classes = new Reflections(packageName).getSubTypesOf(clazz);
for (Class<? extends E> s : classes) {
try {
if (Modifier.isAbstract(s.getModifiers())) {
continue;
}
E instance = s.getConstructor().newInstance();
onInstanciated.accept(instance);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
onFailed.accept(s, e);
}
}
}
public static <E> void loadCommands(Class<E> clazz, Consumer<E> onInstanciated, BiConsumer<Class<?>, Exception> onFailed) {
loadClassesFromPackage("me.bobert.netneutrality.command", clazz, onInstanciated, onFailed);
}
public static <E> void loadCommands(Class<E> clazz, Consumer<E> onInstanciated) {
loadClassesFromPackage("me.bobert.netneutrality.command", clazz, onInstanciated, DEFAULT_FAILURE);
}
public static <E> void loadTasks(Class<E> clazz, Consumer<E> onInstanciated, BiConsumer<Class<?>, Exception> onFailed) {
loadClassesFromPackage("me.bobert.netneutrality.tasks", clazz, onInstanciated, onFailed);
}
public static <E> void loadTasks(Class<E> clazz, Consumer<E> onInstanciated) {
loadClassesFromPackage("me.bobert.netneutrality.tasks", clazz, onInstanciated, DEFAULT_FAILURE);
}
}
@@ -54,6 +54,10 @@ public class Settings {
return INSTANCE;
}
public static String getDiscordInvite() {
return Settings.getInstance().get("NETNEUTRALITY_BOT_INVITE", "https://discord.gg/PyCpd7JGMp");
}
public static String getVersion() {
return Settings.getInstance().getMavenVersion();
}