finish abstraction for StepTask

This commit is contained in:
anthorob committed 2026-08-24 17:11:51 -04:00
1 parent 5ab18ff5ce
commit 87f78ccbea
4 files changed
+250 -56

No files matched your search

@@ -5,8 +5,6 @@ package me.bobert.netneutrality.core.command;
* https://discord.gg/qUa6zNFZsd * https://discord.gg/qUa6zNFZsd
*/ */
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
public abstract class AbstractAdminCommand extends AbstractCommand { public abstract class AbstractAdminCommand extends AbstractCommand {
@Override @Override
@@ -14,11 +12,6 @@ public abstract class AbstractAdminCommand extends AbstractCommand {
return true; return true;
} }
@Override
public DefaultMemberPermissions getSlashCommandPermission() {
return DefaultMemberPermissions.DISABLED;
}
@Override @Override
public CommandCategory getCategory() { public CommandCategory getCategory() {
return CommandCategory.ADMINISTRATION; return CommandCategory.ADMINISTRATION;
@@ -17,11 +17,6 @@ public abstract class AbstractTask implements Task {
creationTime = OffsetDateTime.now(); creationTime = OffsetDateTime.now();
} }
@Override
public boolean isInsertable() {
return true;
}
@Override @Override
public OffsetDateTime getCreationTime() { public OffsetDateTime getCreationTime() {
return creationTime; return creationTime;
@@ -0,0 +1,147 @@
package me.bobert.netneutrality.core.tasks;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
public interface IStepTask {
/**
* Defines the type of input expected for a step.
*/
enum StepType {
/**
* Expects a string input from the user.
*/
STRING,
/**
* Expects a valid role ID for the current guild.
* The role must exist in the guild where the command was executed.
*/
ROLE,
/**
* Expects a valid emoji.
*/
EMOJI
}
class StepResult {
private enum ResultType {
SUCCESS, FAILURE
}
private final ResultType type;
private final String message;
private final Task nextStep;
/**
* Represents the result of a step execution. Each step can either succeed, fail, or transition to another step.
*/
private StepResult(ResultType type, String message, Task nextStep) {
this.type = type;
this.message = message;
this.nextStep = nextStep;
}
/**
* Indicates that the step was completed successfully, with an optional follow-up step.
*
* @param message the success message
* @param nextStep the next step in the workflow, or null if this is the final step
* @return a successful result
*/
public static StepResult success(String message, Task nextStep) {
return new StepResult(ResultType.SUCCESS, message, nextStep);
}
/**
* Indicates that the step was completed successfully.
*
* @param message the success message
* @return a successful result without a next step
*/
public static StepResult success(String message) {
return new StepResult(ResultType.SUCCESS, message, null);
}
/**
* Indicates that the step has failed.
*
* @param message the failure message
* @return a failed result
*/
public static StepResult failure(String message) {
return new StepResult(ResultType.FAILURE, message, null);
}
/**
* Moves directly to the next step without displaying a message.
*
* @param nextTask the next task in the workflow
* @return a successful result that advances to the next step
*/
public static StepResult next(Task nextTask) {
return new StepResult(ResultType.SUCCESS, null, nextTask);
}
/**
* Gets the message associated with the step result.
*
* @return the result message, or null if there is no message
*/
public String getMessage() {
return message;
}
/**
* Gets the next step in the task workflow.
*
* @return the next step, or null if this is the last step
*/
public Task getNextStep() {
return nextStep;
}
/**
* Checks whether the step execution was successful.
*
* @return true if the step was successful, false otherwise
*/
public boolean isSuccess() {
return type == ResultType.SUCCESS;
}
}
/**
* Gets the type of step input expected.
*
* @return the step type
*/
StepType getStepType();
/**
* Provides a general description of the step. This will be displayed before asking the question.
*
* @return the step description
*/
String getStepDescription();
/**
* Provides the specific question for this step.
*
* @return the step question
*/
String getStepQuestion();
/**
* Executes the step using the provided answer.
*
* @param answer the user's input, validated against the expected step type
* @return the result of executing this step
*/
StepResult executeStep(Object answer);
}
@@ -10,23 +10,26 @@ import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.context.MessageContext; import me.bobert.netneutrality.core.context.MessageContext;
import me.bobert.netneutrality.core.context.TaskContext; import me.bobert.netneutrality.core.context.TaskContext;
import me.bobert.netneutrality.core.tasks.AbstractTask; import me.bobert.netneutrality.core.tasks.AbstractTask;
import me.bobert.netneutrality.core.tasks.IStepTask;
import me.bobert.netneutrality.core.tasks.TaskManager; import me.bobert.netneutrality.core.tasks.TaskManager;
import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message; 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.dv8tion.jda.api.interactions.components.buttons.Button;
import org.slf4j.Logger; import net.fellbaum.jemoji.Emoji;
import org.slf4j.LoggerFactory; import net.fellbaum.jemoji.EmojiManager;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public abstract class StepTask extends AbstractTask { /**
private static final Logger logger = LoggerFactory.getLogger(StepTask.class); * 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 enum StepType { */
STRING, ROLE, EMOJI public abstract class StepTask extends AbstractTask implements IStepTask {
} private final Object stepLock = new Object();
private CommandContext commandContext;
private Guild guild; private Guild guild;
private String id = UUID.randomUUID().toString(); private String id = UUID.randomUUID().toString();
@@ -35,45 +38,76 @@ public abstract class StepTask extends AbstractTask {
return id; return id;
} }
@Override
public boolean isInsertable() {
return true;
}
public Guild getGuild() {
return guild;
}
@Override @Override
public void replyToInteraction(CommandContext context) { public void replyToInteraction(CommandContext context) {
synchronized (stepLock) {
this.commandContext = context;
}
if (context.isFromGuild()) { if (context.isFromGuild()) {
guild = context.getGuild(); guild = context.getGuild();
} }
if (guild == null && getStepType() == StepType.ROLE) { if (guild == null && getStepType() == StepType.ROLE) {
throw new IllegalArgumentException("Step task if of type role but it was inserted in a non-guild context"); throw new IllegalArgumentException("Step task is of type role but it was inserted in a non-guild context");
} }
if (inDM()) { context.replyEphemeral("Please follow the instructions sent in DM.");
context.replyEphemeral("Please follow the instructions sent in DM."); context.sendMessageInPrivate(getStepDescription() + "\n\n" + getStepQuestion(),
context.sendMessageInPrivate(getStepDescription() + "\n" + getStepQuestion(), asyncContext -> assignId(asyncContext.getMessageId())); asyncContext -> assignId(asyncContext.getMessageId()));
} else {
context.reply(getStepQuestion(), asyncContext -> id = asyncContext.getMessageId());
}
System.out.println("step id : " + id);
} }
@Override @Override
public Lifecycle execute(TaskContext context) { public Lifecycle execute(TaskContext context) {
if (context instanceof MessageContext messageContext) { synchronized (stepLock) {
Message message = messageContext.getMessage(); if (!context.getDiscordUser().equals(commandContext.getDiscordUser())) {
String answer = message.getContentRaw(); return Lifecycle.KEEP;
if (validateAnswer(answer)) {
StepTask nextStep = executeStep(answer);
//TODO: to finish
} }
} }
return Lifecycle.REMOVE_SELF; if (context instanceof MessageContext messageContext) {
Message message = messageContext.getMessage();
String input = message.getContentRaw();
Object answer = validateInput(context, input);
if (answer == null) {
return Lifecycle.KEEP;
}
StepResult result = executeStep(answer);
if (!result.isSuccess()) {
context.replyEphemeral(result.getMessage());
return Lifecycle.KEEP;
}
if (result.getMessage() != null) {
context.replyEphemeral(result.getMessage());
}
if (result.getNextStep() != null) {
result.getNextStep().insert(commandContext);
}
return Lifecycle.REMOVE_SELF;
}
throw new IllegalArgumentException("Step task is not of type MessageContext");
} }
@Override @Override
public boolean isExpired() { public boolean isExpired() {
return getCreationTime().plusMinutes(30).isBefore(OffsetDateTime.now()); // is it really needed to expire this type of task?
return getCreationTime().plusMinutes(60).isBefore(OffsetDateTime.now());
} }
@Override @Override
@@ -81,8 +115,44 @@ public abstract class StepTask extends AbstractTask {
return null; return null;
} }
private boolean validateAnswer(String answer) { /**
return false; * Validates the user input against the expected step type.
*
* @param context the task execution context
* @param input the user-provided input
* @return the parsed input if valid, or null if invalid
*/
private Object validateInput(TaskContext context, String input) {
switch (getStepType()) {
case ROLE -> {
try {
Role role = guild.getRoleById(input);
if (role == null) {
context.replyEphemeral("The role doesn't exist");
return null;
}
return role;
} catch (NumberFormatException e) {
context.replyEphemeral("Not a valid role ID");
return null;
}
}
case EMOJI -> {
Optional<Emoji> parsed = EmojiManager.getEmoji(input);
if (parsed.isPresent()) {
return parsed.get().getEmoji();
}
context.replyEphemeral("Not a valid emoji");
return null;
}
case STRING -> {
return input;
}
default -> throw new IllegalStateException("Unknown step type: " + getStepType());
}
} }
private void assignId(String newId) { private void assignId(String newId) {
@@ -90,21 +160,10 @@ public abstract class StepTask extends AbstractTask {
throw new IllegalArgumentException("Task ID cannot be null or empty."); throw new IllegalArgumentException("Task ID cannot be null or empty.");
} }
String oldId = id;
synchronized (this) { synchronized (this) {
this.id = newId; this.id = newId;
TaskManager.getInstance().activateTask(this); TaskManager.getInstance().activateTask(this, oldId);
} }
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);
} }