Compare commits

1 Commits
Author SHA1 Message Date
renovate-bot 117d570df1 Update docker/login-action digest to dbcb813
CI / Package (pull_request) Successful in 47s
CI / Docker Build and Push (main) (pull_request) Has been skipped
CI / Docker Build and Push (release) (pull_request) Has been skipped
2026-07-29 13:03:05 +00:00
23 changed files with 13 additions and 1069 deletions

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
.git
.github
.gitea
.idea
.mvn
src
@@ -67,7 +67,7 @@ jobs:
path: .
- name: Log in to registry
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
with:
registry: ${{ secrets.REGISTRY_URL }}
username: ${{ secrets.REGISTRY_USER }}
@@ -108,7 +108,7 @@ jobs:
echo "version=$ver" >> "$GITHUB_OUTPUT"
- name: Log in to registry
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
with:
registry: ${{ secrets.REGISTRY_URL }}
username: ${{ secrets.REGISTRY_USER }}
-5
View File
@@ -226,11 +226,6 @@
<artifactId>jemoji</artifactId>
<version>1.7.6</version>
</dependency>
<dependency>
<groupId>net.fellbaum</groupId>
<artifactId>jemoji</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
+9 -26
View File
@@ -6,6 +6,12 @@
"group:flyway"
],
"packageRules": [
{
"matchPackageNames": [
"quay.io/buildah/stable"
],
"enabled": false
},
{
"matchPackageNames": [
"mariadb"
@@ -13,46 +19,23 @@
"automerge": false,
"dependencyDashboardApproval": true
},
{
"matchManagers": ["maven"],
"extends": ["schedule:monthly"]
},
{
"groupName": "maven plugins",
"matchManagers": ["maven"],
"matchDepTypes": ["plugin"],
"extends": ["schedule:weekly"]
},
{
"groupName": "hibernate orm",
"matchPackageNames": [
"org.hibernate.orm:**"
],
"extends": ["schedule:monthly"]
]
},
{
"groupName": "apache commons",
"matchPackageNames": [
"commons-**",
"org.apache.commons:**"
],
"extends": ["schedule:monthly"]
},
{
"matchManagers": ["maven"],
"matchDepTypes": ["dependency", "parentPom"],
"matchPackageNames": [
"net.dv8tion:JDA",
"dev.arbjerg:lavaplayer",
"dev.lavalink.youtube:**",
"club.minnced:jdave-*"
],
"schedule": ["at any time"]
]
},
{
"groupName": "github actions (minor/patch)",
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["minor", "patch", "digest"]
"matchUpdateTypes": ["minor", "patch"]
},
{
"groupName": "github actions (major)",
@@ -49,7 +49,7 @@ public class NetNeutrality {
options.setEnvironment(env);
options.setTracesSampleRate(1.0);
options.setDebug(false);
options.setRelease(Settings.getVersion());
options.setRelease(VERSION);
});
}
@@ -37,11 +37,6 @@ public class MusicChannelContext extends AbstractContext implements NoMessageCon
return true;
}
@Override
public boolean isFromGuild() {
return true;
}
@Override
public void deleteMessage() {
@@ -1,19 +0,0 @@
package me.bobert.netneutrality.core.command;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
public abstract class AbstractAdminCommand extends AbstractCommand {
@Override
public boolean needAdmin() {
return true;
}
@Override
public CommandCategory getCategory() {
return CommandCategory.ADMINISTRATION;
}
}
@@ -1,56 +0,0 @@
package me.bobert.netneutrality.core.command;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.context.command.MessageMenuContext;
import me.bobert.netneutrality.core.command.options.CommandOptionsBuilder;
import me.bobert.netneutrality.core.context.CommandContext;
import net.dv8tion.jda.api.entities.Message;
public abstract class AbstractMessageMenuCommand extends AbstractCommand {
@Override
public String getDescription() {
return null;
}
@Override
public boolean isGuildOnly() {
return true;
}
@Override
public boolean isSlashCommandOnly() {
return true;
}
@Override
public void registerCommandOptions(CommandOptionsBuilder builder) {
// no options for context commands
}
@Override
public CommandCategory getCategory() {
return CommandCategory.UNLISTED;
}
@Override
public Type getType() {
return Type.MESSAGE;
}
@Override
public CommandExecutionResult execute(CommandContext context) {
if (context instanceof MessageMenuContext mc) {
Message message = mc.getTarget();
return execute(message, context);
}
throw new IllegalArgumentException("Wrong context type for this command");
}
public abstract CommandExecutionResult execute(Message target, CommandContext context);
}
@@ -1,46 +0,0 @@
package me.bobert.netneutrality.core.command;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.context.command.UserMenuContext;
import me.bobert.netneutrality.core.command.options.CommandOptionsBuilder;
import me.bobert.netneutrality.core.context.CommandContext;
import net.dv8tion.jda.api.entities.User;
public abstract class AbstractUserMenuCommand extends AbstractCommand {
@Override
public String getDescription() {
return null;
}
@Override
public boolean isSlashCommandOnly() {
return false;
}
@Override
public void registerCommandOptions(CommandOptionsBuilder builder) {
// no options for context commands
}
@Override
public Type getType() {
return Type.USER;
}
@Override
public CommandExecutionResult execute(CommandContext context) {
if (context instanceof UserMenuContext uc) {
User user = uc.getTarget();
return execute(user, context);
}
throw new IllegalArgumentException("Wrong context type for this command");
}
public abstract CommandExecutionResult execute(User target, CommandContext context);
}
@@ -1,8 +1,4 @@
<<<<<<<< HEAD:src/main/java/me/bobert/netneutrality/core/command/impl/AbstractCommand.java
package me.bobert.netneutrality.core.command.impl;
========
package me.bobert.netneutrality.core.command;
>>>>>>>> 8479f71 (change package of core command classes so it makes more sense and rename getType() to getCategory() in command interface):src/main/java/me/bobert/netneutrality/core/command/AbstractCommand.java
/*
* Copyright (c) anthorob
@@ -1,8 +1,4 @@
<<<<<<<< HEAD:src/main/java/me/bobert/netneutrality/core/command/impl/MusicCommand.java
package me.bobert.netneutrality.core.command.impl;
========
package me.bobert.netneutrality.core.command;
>>>>>>>> 8479f71 (change package of core command classes so it makes more sense and rename getType() to getCategory() in command interface):src/main/java/me/bobert/netneutrality/core/command/AbstractMusicCommand.java
/*
* Copyright (c) anthorob
*
@@ -1,170 +0,0 @@
package me.bobert.netneutrality.core.command.options;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.command.Command;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
import net.dv8tion.jda.api.interactions.commands.build.SubcommandGroupData;
import net.dv8tion.jda.internal.interactions.CommandDataImpl;
import java.util.List;
import java.util.Set;
import java.util.StringJoiner;
public class CommandOptions {
private final Command command;
private final CommandDataImpl commandData;
private final List<OptionData> options;
private final List<SubcommandData> subcommands;
private final List<SubcommandGroupData> subcommandGroups;
public CommandOptions(Command command, CommandDataImpl commandData) {
this.command = command;
this.commandData = commandData;
this.options = commandData.getOptions();
this.subcommands = commandData.getSubcommands();
this.subcommandGroups = commandData.getSubcommandGroups();
}
public Command getCommand() {
return command;
}
public SlashCommandData getSlashCommandData() {
return commandData;
}
public List<OptionData> getOptions() {
return options;
}
public List<SubcommandData> getSubcommands() {
return subcommands;
}
public List<SubcommandGroupData> getSubcommandGroups() {
return subcommandGroups;
}
public boolean isEmpty() {
return options.isEmpty() && subcommands.isEmpty() && subcommandGroups.isEmpty();
}
public String printUsage() {
StringBuilder usage = new StringBuilder();
if (!options.isEmpty()) {
usage.append("- Usage: /").append(command.getCommandName());
for (OptionData option : options) {
usage.append(" <").append(option.getName()).append(">");
}
usage.append(printOptions(options, null, false));
}
if (!subcommands.isEmpty()) {
for (SubcommandData subcommand : subcommands) {
usage.append("- Usage: /").append(command.getCommandName()).append(" ").append(subcommand.getName());
for (OptionData option : subcommand.getOptions()) {
usage.append(" <").append(option.getName()).append(">");
}
usage.append(" (").append(subcommand.getDescription()).append(")");
usage.append(printOptions(subcommand.getOptions(), null, false));
usage.append("\n");
}
}
if (!subcommandGroups.isEmpty()) {
for (SubcommandGroupData subcommandGroup : subcommandGroups) {
usage.append("- Subcommand: /").append(command.getCommandName())
.append(" ").append(subcommandGroup.getName())
.append(" (").append(subcommandGroup.getDescription()).append(")")
.append("\n");
for (SubcommandData subcommand : subcommandGroup.getSubcommands()) {
usage.append(" - Usage: /").append(command.getCommandName())
.append(" ").append(subcommandGroup.getName())
.append(" ").append(subcommand.getName());
for (OptionData option : subcommand.getOptions()) {
usage.append(" <").append(option.getName()).append(">");
}
usage.append(printOptions(subcommand.getOptions(), subcommand.getDescription(), true));
usage.append("\n");
}
}
}
if (!isEmpty()) {
usage.append("\n\n-# *: required argument");
}
return usage.toString();
}
private String printOptions(List<OptionData> options, String description, boolean group) {
StringBuilder usage = new StringBuilder();
if (description != null) {
usage.append(" (").append(description).append(")");
}
for (OptionData option : options) {
usage.append("\n");
if (group) usage.append(" ");
usage.append(" - ");
if (option.isRequired()) {
usage.append("*");
}
usage.append(option.getName()).append(": ").append(option.getDescription());
if (option.getMinValue() != null) {
usage.append(" [Min value: ").append(option.getMinValue().longValue()).append("]");
}
if (option.getMaxValue() != null) {
usage.append(" [Max value: ").append(option.getMaxValue().longValue()).append("]");
}
if (option.getMinLength() != null) {
usage.append(" [Min length: ").append(option.getMinLength().longValue()).append("]");
}
if (option.getMaxLength() != null) {
usage.append(" [Max length: ").append(option.getMaxLength().longValue()).append("]");
}
List<net.dv8tion.jda.api.interactions.commands.Command.Choice> choices = option.getChoices();
if (!choices.isEmpty()) {
StringJoiner joiner = new StringJoiner(", ");
for (net.dv8tion.jda.api.interactions.commands.Command.Choice choice : choices) {
joiner.add(choice.getAsString());
}
usage.append(" [Available choices: ").append(joiner).append("]");
}
Set<ChannelType> requiredChannelTypes = option.getChannelTypes();
if (!requiredChannelTypes.isEmpty()) {
StringJoiner joiner = new StringJoiner(", ");
for (ChannelType type : requiredChannelTypes) {
joiner.add(type.name());
}
usage.append(" [Required channel types: ").append(joiner).append("]");
}
}
return usage.toString();
}
}
@@ -1,185 +0,0 @@
package me.bobert.netneutrality.core.command.options;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.command.Command;
import net.dv8tion.jda.api.interactions.DiscordLocale;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
import net.dv8tion.jda.api.interactions.commands.build.*;
import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction;
import net.dv8tion.jda.api.interactions.commands.localization.LocalizationMap;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.interactions.CommandDataImpl;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
public class CommandOptionsBuilder implements SlashCommandData {
private final Command command;
private CommandDataImpl commandData;
public CommandOptionsBuilder(Command.Type type, Command command) {
this.command = command;
if (type == Command.Type.SLASH) {
commandData = (CommandDataImpl) Commands.slash(command.getCommandName(), command.getDescription());
}
if (type == Command.Type.USER) {
commandData = (CommandDataImpl) Commands.user(command.getCommandName());
}
if (type == Command.Type.MESSAGE) {
commandData = (CommandDataImpl) Commands.message(command.getCommandName());
}
}
public CommandOptions build() {
return new CommandOptions(command, commandData);
}
@Override
public @Nonnull SlashCommandData setLocalizationFunction(@Nonnull LocalizationFunction localizationFunction) {
return commandData.setLocalizationFunction(localizationFunction);
}
@Override
public @Nonnull SlashCommandData setName(@Nonnull String name) {
return commandData.setName(name);
}
@Override
public @Nonnull SlashCommandData setNameLocalization(@Nonnull DiscordLocale locale, @Nonnull String name) {
return commandData.setNameLocalization(locale, name);
}
@Override
public @Nonnull SlashCommandData setNameLocalizations(@Nonnull Map<DiscordLocale, String> map) {
return commandData.setNameLocalizations(map);
}
@Override
public @Nonnull SlashCommandData setDefaultPermissions(@Nonnull DefaultMemberPermissions permission) {
return commandData.setDefaultPermissions(permission);
}
@Override
public @Nonnull SlashCommandData setGuildOnly(boolean guildOnly) {
return commandData.setGuildOnly(guildOnly);
}
@Override
public @Nonnull SlashCommandData setNSFW(boolean nsfw) {
return commandData.setNSFW(nsfw);
}
@Override
public @Nonnull String getName() {
return commandData.getName();
}
@Override
public @Nonnull LocalizationMap getNameLocalizations() {
return commandData.getNameLocalizations();
}
@Override
public @Nonnull net.dv8tion.jda.api.interactions.commands.Command.Type getType() {
return commandData.getType();
}
@Override
public @Nonnull DefaultMemberPermissions getDefaultPermissions() {
return commandData.getDefaultPermissions();
}
@Override
public boolean isGuildOnly() {
return commandData.isGuildOnly();
}
@Override
public boolean isNSFW() {
return commandData.isNSFW();
}
@Override
public @Nonnull SlashCommandData setDescription(@Nonnull String description) {
return commandData.setDescription(description);
}
@Override
public @Nonnull SlashCommandData setDescriptionLocalization(@Nonnull DiscordLocale locale, @Nonnull String description) {
return commandData.setDescriptionLocalization(locale, description);
}
@Override
public @Nonnull SlashCommandData setDescriptionLocalizations(@Nonnull Map<DiscordLocale, String> map) {
return commandData.setDescriptionLocalizations(map);
}
@Override
public @Nonnull String getDescription() {
return commandData.getDescription();
}
@Override
public @Nonnull LocalizationMap getDescriptionLocalizations() {
return commandData.getDescriptionLocalizations();
}
@Override
public boolean removeOptions(@Nonnull Predicate<? super OptionData> condition) {
return commandData.removeOptions(condition);
}
@Override
public boolean removeSubcommands(@Nonnull Predicate<? super SubcommandData> condition) {
return commandData.removeSubcommands(condition);
}
@Override
public boolean removeSubcommandGroups(@Nonnull Predicate<? super SubcommandGroupData> condition) {
return commandData.removeSubcommandGroups(condition);
}
@Override
public @Nonnull List<SubcommandData> getSubcommands() {
return commandData.getSubcommands();
}
@Override
public @Nonnull List<SubcommandGroupData> getSubcommandGroups() {
return commandData.getSubcommandGroups();
}
@Override
public @Nonnull List<OptionData> getOptions() {
return commandData.getOptions();
}
@Override
public @Nonnull SlashCommandData addOptions(@Nonnull OptionData... options) {
return commandData.addOptions(options);
}
@Override
public @Nonnull SlashCommandData addSubcommands(@Nonnull SubcommandData... subcommands) {
return commandData.addSubcommands(subcommands);
}
@Override
public @Nonnull SlashCommandData addSubcommandGroups(@Nonnull SubcommandGroupData... groups) {
return commandData.addSubcommandGroups(groups);
}
@Override
public @Nonnull DataObject toData() {
return commandData.toData();
}
}
@@ -6,7 +6,6 @@ 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;
@@ -1,40 +0,0 @@
package me.bobert.netneutrality.core.tasks;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.context.TaskContext;
public abstract class AbstractBuiltInTask extends AbstractTask implements BuiltInTask {
@Override
public String getName() {
return getId();
}
@Override
public boolean isInsertable() {
return true;
}
@Override
public boolean isExpired() {
return false;
}
@Override
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);
}
@@ -1,58 +0,0 @@
package me.bobert.netneutrality.core.tasks;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.context.CommandContext;
import org.json.JSONObject;
public abstract class AbstractPersistableTask extends AbstractTask implements PersistableTask {
@Override
public boolean isInsertable() {
return true;
}
@Override
public boolean isExpired() {
return false;
}
@Override
public void replyToInteraction(CommandContext context) {
// 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);
return payload.toString();
}
}
@@ -1,48 +0,0 @@
package me.bobert.netneutrality.core.tasks;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.context.CommandContext;
import java.time.OffsetDateTime;
import java.util.Objects;
public abstract class AbstractTask implements Task {
private final OffsetDateTime creationTime;
public AbstractTask() {
creationTime = OffsetDateTime.now();
}
@Override
public OffsetDateTime getCreationTime() {
return creationTime;
}
@Override
public void insert(CommandContext context) {
TaskManager.getInstance().insert(context, this);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AbstractTask that = (AbstractTask) o;
return Objects.equals(getId(), that.getId());
}
@Override
public int hashCode() {
return Objects.hashCode(getId());
}
@Override
public String toString() {
return String.format("Task{id=%s, lockId=%s, class=%s, name=%s, creationTime=%s}", getId(), getLockId(), getClass().getSimpleName(), getName(), getCreationTime());
}
}
@@ -1,27 +0,0 @@
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;
}
}
@@ -1,15 +0,0 @@
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());
}
}
@@ -1,147 +0,0 @@
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);
}
@@ -1,169 +0,0 @@
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.IStepTask;
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;
import java.time.OffsetDateTime;
import java.util.Optional;
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 {
private final Object stepLock = new Object();
private CommandContext commandContext;
private Guild guild;
private String id = UUID.randomUUID().toString();
@Override
public String getId() {
return id;
}
@Override
public boolean isInsertable() {
return true;
}
public Guild getGuild() {
return guild;
}
@Override
public void replyToInteraction(CommandContext context) {
synchronized (stepLock) {
this.commandContext = context;
}
if (context.isFromGuild()) {
guild = context.getGuild();
}
if (guild == null && getStepType() == StepType.ROLE) {
throw new IllegalArgumentException("Step task is of type role but it was inserted in a non-guild context");
}
context.replyEphemeral("Please follow the instructions sent in DM.");
context.sendMessageInPrivate(getStepDescription() + "\n\n" + getStepQuestion(),
asyncContext -> assignId(asyncContext.getMessageId()));
}
@Override
public Lifecycle execute(TaskContext context) {
synchronized (stepLock) {
if (!context.getDiscordUser().equals(commandContext.getDiscordUser())) {
return Lifecycle.KEEP;
}
}
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
public boolean isExpired() {
// is it really needed to expire this type of task?
return getCreationTime().plusMinutes(60).isBefore(OffsetDateTime.now());
}
@Override
public Button createButton() {
return null;
}
/**
* 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) {
if (newId == null || newId.isBlank()) {
throw new IllegalArgumentException("Task ID cannot be null or empty.");
}
String oldId = id;
synchronized (this) {
this.id = newId;
TaskManager.getInstance().activateTask(this, oldId);
}
}
}
@@ -13,7 +13,6 @@ 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;
@@ -1,39 +0,0 @@
package me.bobert.netneutrality.thread;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
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);
@Override
public void run() {
try {
for (MusicManager manager : MusicManager.getManagers()) {
processManager(manager);
}
} catch (InterruptedException e) {
logger.error("Interrupted while running MusicManagerCleanupThread", e);
}
}
private void processManager(MusicManager manager) throws InterruptedException {
TrackScheduler scheduler = manager.getScheduler();
// Context is still attached after bot is disconnected, the bot might have been kicked from the channel
if (scheduler.getContext() != null && !manager.isConnected()) {
manager.disconnect();
manager.getQueueManager().clearQueue();
}
}
}