Bunch of improvements to tasks and DB logic

Use locks when executing tasks
Decouple GuildManager into services
Add more logging when failing to load a persistable task
Add ping method to WebModule
Improve Task interface and implementations
Start work on step tasks
This commit is contained in:
anthorob committed 2026-08-24 17:10:53 -04:00
1 parent e58854a85c
commit d0c06cd6bf
15 files changed
+204 -224

No files matched your search

@@ -49,7 +49,7 @@ public class NetNeutrality {
options.setEnvironment(env);
options.setTracesSampleRate(1.0);
options.setDebug(false);
options.setRelease(VERSION);
options.setRelease(Settings.getVersion());
});
}
@@ -17,9 +17,14 @@ public abstract class AbstractMessageMenuCommand extends AbstractCommand {
return null;
}
@Override
public boolean isGuildOnly() {
return true;
}
@Override
public boolean isSlashCommandOnly() {
return false;
return true;
}
@Override
@@ -27,6 +32,11 @@ public abstract class AbstractMessageMenuCommand extends AbstractCommand {
// no options for context commands
}
@Override
public CommandCategory getCategory() {
return CommandCategory.UNLISTED;
}
@Override
public Type getType() {
return Type.MESSAGE;
@@ -6,6 +6,7 @@ package me.bobert.netneutrality.core.context;
*/
import io.sentry.Sentry;
import me.bobert.netneutrality.context.command.SlashCommandContext;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
@@ -6,6 +6,7 @@ package me.bobert.netneutrality.core.tasks;
*/
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.context.TaskContext;
public abstract class AbstractBuiltInTask extends AbstractTask implements BuiltInTask {
@@ -19,11 +20,6 @@ public abstract class AbstractBuiltInTask extends AbstractTask implements BuiltI
return true;
}
@Override
public boolean oneshot() {
return false;
}
@Override
public boolean isExpired() {
return false;
@@ -33,4 +29,12 @@ public abstract class AbstractBuiltInTask extends AbstractTask implements BuiltI
public void replyToInteraction(CommandContext context) {
// Task is loaded at runtime, this will never be called
}
@Override
public Lifecycle execute(TaskContext context) {
executeTask(context);
return Lifecycle.BUILTIN;
}
public abstract void executeTask(TaskContext context);
}
@@ -15,11 +15,6 @@ public abstract class AbstractPersistableTask extends AbstractTask implements Pe
return true;
}
@Override
public boolean oneshot() {
return false;
}
@Override
public boolean isExpired() {
return false;
@@ -30,6 +25,30 @@ public abstract class AbstractPersistableTask extends AbstractTask implements Pe
// Task loaded from database, this will never be called
}
@Override
public void insert(CommandContext context) {
if (TaskManager.getInstance().hasTask(getId())) {
throw new RuntimeException("Cannot insert a task that already exists");
}
TaskManager.getInstance().insert(context, this);
}
@Override
public void save() {
if (!TaskManager.getInstance().hasTask(getId())) {
throw new RuntimeException("Cannot save a task that doesn't exist yet");
}
TaskManager.getInstance().getTaskService().persistTask(this);
}
@Override
public void delete() {
TaskManager.getInstance().getTaskService().unpersistTask(this);
TaskManager.getInstance().removeTask(this);
}
@Override
public String toPayload() {
JSONObject payload = new JSONObject(this);
@@ -5,6 +5,8 @@ package me.bobert.netneutrality.core.tasks;
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.context.CommandContext;
import java.time.OffsetDateTime;
import java.util.Objects;
@@ -26,8 +28,8 @@ public abstract class AbstractTask implements Task {
}
@Override
public TaskButtonCreator createButton() {
return new TaskButtonCreator(getId());
public void insert(CommandContext context) {
TaskManager.getInstance().insert(context, this);
}
@Override
@@ -46,6 +48,6 @@ public abstract class AbstractTask implements Task {
@Override
public String toString() {
return String.format("Task{id=%s, class=%s, name=%s, oneshot=%b, creationTime=%s}", getId(), getClass().getSimpleName(), getName(), oneshot(), getCreationTime());
return String.format("Task{id=%s, lockId=%s, class=%s, name=%s, creationTime=%s}", getId(), getLockId(), getClass().getSimpleName(), getName(), getCreationTime());
}
}
@@ -0,0 +1,27 @@
package me.bobert.netneutrality.core.tasks;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import net.dv8tion.jda.api.interactions.components.buttons.Button;
public abstract class GenericCustomIdTask extends AbstractTask {
private final String customId;
public GenericCustomIdTask(String id) {
this.customId = id;
}
@Override
public String getId() {
return customId;
}
@Override
public Button createButton() {
return null;
}
}
@@ -0,0 +1,15 @@
package me.bobert.netneutrality.core.tasks;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import java.util.UUID;
public abstract class GenericRandomIdTask extends GenericCustomIdTask {
public GenericRandomIdTask() {
super(UUID.randomUUID().toString());
}
}
@@ -0,0 +1,110 @@
package me.bobert.netneutrality.core.tasks.impl;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
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.TaskManager;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.OffsetDateTime;
import java.util.UUID;
public abstract class StepTask extends AbstractTask {
private static final Logger logger = LoggerFactory.getLogger(StepTask.class);
public enum StepType {
STRING, ROLE, EMOJI
}
private Guild guild;
private String id = UUID.randomUUID().toString();
@Override
public String getId() {
return id;
}
@Override
public void replyToInteraction(CommandContext context) {
if (context.isFromGuild()) {
guild = context.getGuild();
}
if (guild == null && getStepType() == StepType.ROLE) {
throw new IllegalArgumentException("Step task if of type role but it was inserted in a non-guild context");
}
if (inDM()) {
context.replyEphemeral("Please follow the instructions sent in DM.");
context.sendMessageInPrivate(getStepDescription() + "\n" + getStepQuestion(), asyncContext -> assignId(asyncContext.getMessageId()));
} else {
context.reply(getStepQuestion(), asyncContext -> id = asyncContext.getMessageId());
}
System.out.println("step id : " + id);
}
@Override
public Lifecycle execute(TaskContext context) {
if (context instanceof MessageContext messageContext) {
Message message = messageContext.getMessage();
String answer = message.getContentRaw();
if (validateAnswer(answer)) {
StepTask nextStep = executeStep(answer);
//TODO: to finish
}
}
return Lifecycle.REMOVE_SELF;
}
@Override
public boolean isExpired() {
return getCreationTime().plusMinutes(30).isBefore(OffsetDateTime.now());
}
@Override
public Button createButton() {
return null;
}
private boolean validateAnswer(String answer) {
}
private void assignId(String newId) {
if (newId == null || newId.isBlank()) {
throw new IllegalArgumentException("Task ID cannot be null or empty.");
}
synchronized (this) {
this.id = newId;
TaskManager.getInstance().activateTask(this);
}
this.id = newId;
}
public abstract boolean inDM();
public abstract StepType getStepType();
public abstract String getStepDescription();
public abstract String getStepQuestion();
public abstract StepTask executeStep(String answer);
}
@@ -1,30 +0,0 @@
package me.bobert.netneutrality.tasks;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.tasks.AbstractTask;
import java.time.OffsetDateTime;
import java.util.UUID;
public abstract class ButtonTask extends AbstractTask {
private final UUID id = UUID.randomUUID();
@Override
public String getId() {
return id.toString();
}
@Override
public boolean oneshot() {
return true;
}
@Override
public boolean isExpired() {
return getCreationTime().plusMinutes(15).isBefore(OffsetDateTime.now());
}
}
@@ -1,31 +0,0 @@
package me.bobert.netneutrality.tasks;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.tasks.AbstractPersistableTask;
public abstract class EmojiTask extends AbstractPersistableTask {
private final String guildId;
private final String messageId;
public EmojiTask(String guildId, String messageId) {
this.guildId = guildId;
this.messageId = messageId;
}
public String getGuildId() {
return guildId;
}
public String getMessageId() {
return messageId;
}
@Override
public String getId() {
return String.format("%s:%s", getGuildId(), getMessageId());
}
}
@@ -1,42 +0,0 @@
package me.bobert.netneutrality.tasks;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.AbstractTask;
import net.dv8tion.jda.api.interactions.modals.ModalMapping;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
public abstract class ModalTask extends AbstractTask {
private final UUID id = UUID.randomUUID();
@Override
public String getId() {
return id.toString();
}
@Override
public boolean oneshot() {
return true;
}
@Override
public boolean isExpired() {
return getCreationTime().plusMinutes(5).isBefore(OffsetDateTime.now());
}
public ModalMapping getValue(String key, TaskContext context) {
List<?> mappings = (List<?>) context.getContextData("values");
return mappings.stream()
.filter(o -> o instanceof ModalMapping)
.map(o -> (ModalMapping) o)
.filter(mapping -> mapping.getId().equals(key))
.findFirst().orElse(null);
}
}
@@ -1,61 +0,0 @@
package me.bobert.netneutrality.tasks.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.trait.WithParent;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.music.SourceFinder;
import me.bobert.netneutrality.tasks.ButtonTask;
import me.bobert.netneutrality.utils.MessageFunction;
import me.bobert.netneutrality.utils.MusicUtils;
import net.dv8tion.jda.api.entities.Member;
import java.util.List;
public class AddAllTracksTask extends ButtonTask implements WithParent<LoadPlaylistTask> {
private final LoadPlaylistTask parentTask;
List<AudioTrack> tracks;
public AddAllTracksTask(LoadPlaylistTask parentTask, List<AudioTrack> tracks) {
this.parentTask = parentTask;
this.tracks = tracks;
}
@Override
public String getName() {
return "add-all-tracks";
}
@Override
public void replyToInteraction(CommandContext context) {
}
@Override
public void execute(TaskContext context) {
MusicManager manager = MusicManager.get(context.getGuild());
Member member = context.getGuildUser();
if (MusicUtils.connect(context)) {
manager.getQueueManager().queue(tracks, member);
context.reply(MessageFunction.buildAddingPlaylistMessage(tracks, parentTask.getPlaylist().getName(), member, SourceFinder.AudioSourceType.Youtube));
}
}
@Override
public LoadPlaylistTask getParentTask() {
return parentTask;
}
@Override
public boolean shouldKeepChilds() {
return false;
}
}
@@ -1,45 +0,0 @@
package me.bobert.netneutrality.tasks.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.trait.NotAcknowledgeable;
import me.bobert.netneutrality.core.tasks.trait.WithParent;
import me.bobert.netneutrality.tasks.ButtonTask;
public class CancelLoadTask extends ButtonTask implements WithParent<LoadPlaylistTask>, NotAcknowledgeable {
private final LoadPlaylistTask parentTask;
public CancelLoadTask(LoadPlaylistTask parentTask) {
this.parentTask = parentTask;
}
@Override
public String getName() {
return "cancel-playlist-loading";
}
@Override
public void replyToInteraction(CommandContext context) {
}
@Override
public LoadPlaylistTask getParentTask() {
return parentTask;
}
@Override
public boolean shouldKeepChilds() {
return false;
}
@Override
public void execute(TaskContext context) {
// Interaction will get removed after execution
}
}
@@ -13,6 +13,7 @@ import me.bobert.netneutrality.utils.MessageFunction;
import me.bobert.netneutrality.utils.MusicUtils;
import net.dv8tion.jda.api.components.buttons.ButtonStyle;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
public class LoadTrackFromPlaylistTask extends AcceptButton<LoadPlaylistTask> {
private final AudioTrack track;