From fa62353aee86c9f69fd713d5f1495da01b141d01 Mon Sep 17 00:00:00 2001 From: Sheldan <5037282+Sheldan@users.noreply.github.com> Date: Sun, 15 Feb 2026 22:44:57 +0100 Subject: [PATCH] [AB-xxx] adding support to use @time inputs for duration and instant command parameters --- .../remind/service/RemindServiceBean.java | 3 +- .../InstantSlashCommandParameterProvider.java | 2 +- .../exception/DurationFormatException.java | 4 +- .../exception/InstantFormatException.java | 28 +++++++++ .../InstantFormatExceptionModel.java | 13 ++++ .../abstracto/core/utils/ParseUtils.java | 61 ++++++++++++++----- abstracto-application/pom.xml | 2 +- 7 files changed, 93 insertions(+), 20 deletions(-) create mode 100644 abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/exception/InstantFormatException.java create mode 100644 abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/exception/InstantFormatExceptionModel.java diff --git a/abstracto-application/abstracto-modules/remind/remind-impl/src/main/java/dev/sheldan/abstracto/remind/service/RemindServiceBean.java b/abstracto-application/abstracto-modules/remind/remind-impl/src/main/java/dev/sheldan/abstracto/remind/service/RemindServiceBean.java index f40616316..278280f93 100644 --- a/abstracto-application/abstracto-modules/remind/remind-impl/src/main/java/dev/sheldan/abstracto/remind/service/RemindServiceBean.java +++ b/abstracto-application/abstracto-modules/remind/remind-impl/src/main/java/dev/sheldan/abstracto/remind/service/RemindServiceBean.java @@ -130,13 +130,14 @@ public class RemindServiceBean implements ReminderService { if(remindIn.getSeconds() < 60) { reminder.setJobTriggerKey(null); log.info("Directly scheduling unremind for reminder {}, because it was below the threshold.", reminder.getId()); + long nanos = Math.max(remindIn.toNanos(), Duration.ofSeconds(5).toNanos()); // should be good enough, if its too small, it doesnt find the reminder instantReminderScheduler.schedule(() -> { try { self.executeReminder(reminder.getId()); } catch (Exception exception) { log.error("Failed to remind immediately.", exception); } - }, remindIn.toNanos(), TimeUnit.NANOSECONDS); + }, nanos, TimeUnit.NANOSECONDS); } else { HashMap parameters = new HashMap<>(); parameters.put("reminderId", reminder.getId().toString()); diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/parameter/provider/provided/InstantSlashCommandParameterProvider.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/parameter/provider/provided/InstantSlashCommandParameterProvider.java index 8bb8a9646..51b0f2f59 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/parameter/provider/provided/InstantSlashCommandParameterProvider.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/parameter/provider/provided/InstantSlashCommandParameterProvider.java @@ -15,7 +15,7 @@ public class InstantSlashCommandParameterProvider implements SlashCommandParamet return SlashCommandOptionTypeMapping .builder() .type(Instant.class) - .optionTypes(Arrays.asList(OptionType.INTEGER)) + .optionTypes(Arrays.asList(OptionType.STRING)) .build(); } } diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/exception/DurationFormatException.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/exception/DurationFormatException.java index 2cc9ed4ea..6e617a259 100644 --- a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/exception/DurationFormatException.java +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/exception/DurationFormatException.java @@ -10,11 +10,11 @@ public class DurationFormatException extends AbstractoRunTimeException implement private final DurationFormatExceptionModel model; - public DurationFormatException(String wrongFormat, List validFormats) { + public DurationFormatException(String invalidFormat, List validFormats) { super("Duration format exception "); this.model = DurationFormatExceptionModel .builder() - .invalidFormat(wrongFormat) + .invalidFormat(invalidFormat) .validFormats(validFormats) .build(); } diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/exception/InstantFormatException.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/exception/InstantFormatException.java new file mode 100644 index 000000000..8a2d9dd6e --- /dev/null +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/exception/InstantFormatException.java @@ -0,0 +1,28 @@ +package dev.sheldan.abstracto.core.exception; + +import dev.sheldan.abstracto.core.models.exception.InstantFormatExceptionModel; +import dev.sheldan.abstracto.core.templating.Templatable; + +public class InstantFormatException extends AbstractoRunTimeException implements Templatable { + + private final InstantFormatExceptionModel model; + + + public InstantFormatException(String invalidFormat) { + super("Instant format exception "); + this.model = InstantFormatExceptionModel + .builder() + .invalidFormat(invalidFormat) + .build(); + } + + @Override + public String getTemplateName() { + return "instant_invalid_time_format_exception"; + } + + @Override + public Object getTemplateModel() { + return model; + } +} diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/exception/InstantFormatExceptionModel.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/exception/InstantFormatExceptionModel.java new file mode 100644 index 000000000..004e52804 --- /dev/null +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/exception/InstantFormatExceptionModel.java @@ -0,0 +1,13 @@ +package dev.sheldan.abstracto.core.models.exception; + +import java.io.Serializable; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +@Builder +public class InstantFormatExceptionModel implements Serializable { + private final String invalidFormat; +} diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/utils/ParseUtils.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/utils/ParseUtils.java index 7ba84a63a..0780c4cb5 100644 --- a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/utils/ParseUtils.java +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/utils/ParseUtils.java @@ -3,12 +3,18 @@ package dev.sheldan.abstracto.core.utils; import dev.sheldan.abstracto.core.command.exception.AbstractoTemplatedException; import dev.sheldan.abstracto.core.exception.DurationFormatException; +import dev.sheldan.abstracto.core.exception.InstantFormatException; +import java.time.Instant; +import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.ISnowflake; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; +import net.dv8tion.jda.api.utils.TimeFormat; +import net.dv8tion.jda.api.utils.Timestamp; import net.dv8tion.jda.api.utils.cache.SnowflakeCacheView; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import java.time.Duration; @@ -18,40 +24,65 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +@Slf4j public class ParseUtils { private ParseUtils() { } - private static Pattern messageRegex = Pattern.compile("(?\\d+)(?[ywdhms]+)"); - private static List validDuration = Arrays.asList("w", "d", "h", "m", "s"); + private static final Pattern MESSAGE_REGEX = Pattern.compile("(?\\d+)(?[ywdhms]+)"); + private static final List VALID_DURATION = Arrays.asList("w", "d", "h", "m", "s"); public static Duration parseDuration(String textToParseFrom) { if(textToParseFrom == null || textToParseFrom.isEmpty()) { - throw new DurationFormatException("", validDuration); + throw new DurationFormatException("", VALID_DURATION); } - Matcher matcher = ParseUtils.messageRegex.matcher(textToParseFrom); - Duration start = Duration.ZERO; + Duration targetDuration; + try { + Timestamp discordTimeStamp = TimeFormat.parse(textToParseFrom); + targetDuration = Duration.between(Instant.now(), discordTimeStamp.toInstant()); + return targetDuration; + } catch (IllegalArgumentException ex) { + // ignore + } + Matcher matcher = ParseUtils.MESSAGE_REGEX.matcher(textToParseFrom); + targetDuration = Duration.ZERO; String rest = textToParseFrom; while(matcher.find()) { String unit = matcher.group("unit"); String number = matcher.group("number"); rest = rest.replace(matcher.group(0), ""); long parsed = Long.parseLong(number); - switch (unit) { - case "w": start = start.plus(Duration.ofDays(parsed * 7)); break; - case "d": start = start.plus(Duration.ofDays(parsed)); break; - case "h": start = start.plus(Duration.ofHours(parsed)); break; - case "m": start = start.plus(Duration.ofMinutes(parsed)); break; - case "s": start = start.plus(Duration.ofSeconds(parsed)); break; - default: throw new DurationFormatException(unit, validDuration); - } + targetDuration = switch (unit) { + case "w" -> targetDuration.plus(Duration.ofDays(parsed * 7)); + case "d" -> targetDuration.plus(Duration.ofDays(parsed)); + case "h" -> targetDuration.plus(Duration.ofHours(parsed)); + case "m" -> targetDuration.plus(Duration.ofMinutes(parsed)); + case "s" -> targetDuration.plus(Duration.ofSeconds(parsed)); + default -> throw new DurationFormatException(unit, VALID_DURATION); + }; } if(!rest.equals("")) { - throw new DurationFormatException(rest, validDuration); + throw new DurationFormatException(rest, VALID_DURATION); } - return start; + return targetDuration; + } + + public static Instant parseInstant(String textToParseFrom) { + if(textToParseFrom == null || textToParseFrom.isEmpty()) { + throw new DurationFormatException("", VALID_DURATION); + } + try { + Timestamp discordTimeStamp = TimeFormat.parse(textToParseFrom); + return discordTimeStamp.toInstant(); + } catch (IllegalArgumentException ex) { + // ignore + } + if(StringUtils.isNumeric(textToParseFrom)) { + return Instant.ofEpochSecond(Integer.parseInt(textToParseFrom)); + } + throw new InstantFormatException(textToParseFrom); } public static Role parseRoleFromText(String text, Guild guild) { diff --git a/abstracto-application/pom.xml b/abstracto-application/pom.xml index df3e3eda4..69020b131 100644 --- a/abstracto-application/pom.xml +++ b/abstracto-application/pom.xml @@ -55,7 +55,7 @@ yyyy/MM/dd HH:mm - 6.2.0 + 6.3.1 2.2.6 1.5.3 2.3.0