From 0653001b032fbc714f788063166db581de5fd8d4 Mon Sep 17 00:00:00 2001 From: av Date: Sun, 10 May 2026 01:06:14 +0300 Subject: [PATCH 1/8] improved: added module package with MOTD as module reasons: - decouple logic model from config model - store parsed time ranges without parsing them every time --- .../jpenilla/minimotd/common/model/MOTD.java | 23 ++++ .../model/timerange/AbstractTimeRange.java | 28 +++++ .../timerange/ExactUTCDateTimeRange.java | 19 ++++ .../model/timerange/OnlyUTCTimeRange.java | 21 ++++ .../common/model/timerange/TimeRange.java | 11 ++ .../model/timerange/TimeRangeException.java | 7 ++ .../model/timerange/TimeRangeFactory.java | 104 ++++++++++++++++++ 7 files changed, 213 insertions(+) create mode 100644 common/src/main/java/xyz/jpenilla/minimotd/common/model/MOTD.java create mode 100644 common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/AbstractTimeRange.java create mode 100644 common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/ExactUTCDateTimeRange.java create mode 100644 common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/OnlyUTCTimeRange.java create mode 100644 common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/TimeRange.java create mode 100644 common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/TimeRangeException.java create mode 100644 common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/TimeRangeFactory.java diff --git a/common/src/main/java/xyz/jpenilla/minimotd/common/model/MOTD.java b/common/src/main/java/xyz/jpenilla/minimotd/common/model/MOTD.java new file mode 100644 index 00000000..54e302da --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/minimotd/common/model/MOTD.java @@ -0,0 +1,23 @@ +package xyz.jpenilla.minimotd.common.model; + +import org.jspecify.annotations.NonNull; +import xyz.jpenilla.minimotd.common.model.timerange.TimeRange; +import java.util.List; + +public interface MOTD { + @NonNull + String line1(); + + @NonNull + String line2(); + + @NonNull + String icon(); + + @NonNull + List timeRanges(); + + int weight(); + + int priority(); +} diff --git a/common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/AbstractTimeRange.java b/common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/AbstractTimeRange.java new file mode 100644 index 00000000..b61f2501 --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/AbstractTimeRange.java @@ -0,0 +1,28 @@ +package xyz.jpenilla.minimotd.common.model.timerange; + +import org.jspecify.annotations.NonNull; + +abstract class AbstractTimeRange> implements TimeRange { + @NonNull + protected final UNIT from; + + @NonNull + protected final UNIT to; + + AbstractTimeRange(final @NonNull UNIT from, final @NonNull UNIT to) { + if (from.compareTo(to) > 0) { + throw new TimeRangeException("Invalid time range: [%s, %s]".formatted(from, to)); + } + + this.from = from; + this.to = to; + } + + public final @NonNull UNIT from() { + return from; + } + + public final @NonNull UNIT to() { + return to; + } +} diff --git a/common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/ExactUTCDateTimeRange.java b/common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/ExactUTCDateTimeRange.java new file mode 100644 index 00000000..cb918838 --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/ExactUTCDateTimeRange.java @@ -0,0 +1,19 @@ +package xyz.jpenilla.minimotd.common.model.timerange; + +import org.jspecify.annotations.NonNull; +import java.time.*; + +final class ExactUTCDateTimeRange extends AbstractTimeRange { + + ExactUTCDateTimeRange(final @NonNull Instant fromUTC, final @NonNull Instant toUTC) { + super(fromUTC, toUTC); + } + + @Override + public boolean isInTimeRange(final @NonNull OffsetDateTime now) { + final var nowUTC = now.toInstant(); + + return (from.isBefore(nowUTC) || from.equals(nowUTC)) + && (to.isAfter(nowUTC) || to.equals(nowUTC)); + } +} diff --git a/common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/OnlyUTCTimeRange.java b/common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/OnlyUTCTimeRange.java new file mode 100644 index 00000000..1c55b564 --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/OnlyUTCTimeRange.java @@ -0,0 +1,21 @@ +package xyz.jpenilla.minimotd.common.model.timerange; + +import org.jspecify.annotations.NonNull; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; + +final class OnlyUTCTimeRange extends AbstractTimeRange { + + OnlyUTCTimeRange(final @NonNull LocalTime from, final @NonNull LocalTime to) { + super(from, to); + } + + @Override + public boolean isInTimeRange(@NonNull OffsetDateTime now) { + final LocalTime nowTimeUTC = now.withOffsetSameInstant(ZoneOffset.UTC).toLocalTime(); + + return (from.isBefore(nowTimeUTC) || from.equals(nowTimeUTC)) + && (to.isAfter(nowTimeUTC) || to.equals(nowTimeUTC)); + } +} diff --git a/common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/TimeRange.java b/common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/TimeRange.java new file mode 100644 index 00000000..b718b343 --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/TimeRange.java @@ -0,0 +1,11 @@ +package xyz.jpenilla.minimotd.common.model.timerange; + +import java.time.OffsetDateTime; +import org.jspecify.annotations.NonNull; + +public interface TimeRange { + /** + * Intentionally designed to be aware about timezone to be able to support functionality of relying on user time zone in future + */ + boolean isInTimeRange(@NonNull final OffsetDateTime now); +} diff --git a/common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/TimeRangeException.java b/common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/TimeRangeException.java new file mode 100644 index 00000000..2b63933f --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/TimeRangeException.java @@ -0,0 +1,7 @@ +package xyz.jpenilla.minimotd.common.model.timerange; + +public class TimeRangeException extends RuntimeException { + public TimeRangeException(String message) { + super(message); + } +} diff --git a/common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/TimeRangeFactory.java b/common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/TimeRangeFactory.java new file mode 100644 index 00000000..c19a49bc --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/minimotd/common/model/timerange/TimeRangeFactory.java @@ -0,0 +1,104 @@ +package xyz.jpenilla.minimotd.common.model.timerange; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalAccessor; +import org.jspecify.annotations.NonNull; + +public final class TimeRangeFactory { + + private static final DateTimeFormatter ISO_LOCAL_DATE_TIME_OPTIONAL_OFFSET = new DateTimeFormatterBuilder() + .append(DateTimeFormatter.ISO_LOCAL_DATE) + .appendLiteral('T') + .append(DateTimeFormatter.ISO_LOCAL_TIME) + .optionalStart() + .appendOffsetId() + .optionalEnd() + .toFormatter(); + + private static final DateTimeFormatter ISO_LOCAL_TIME_OPTIONAL_OFFSET = new DateTimeFormatterBuilder() + .append(DateTimeFormatter.ISO_LOCAL_TIME) + .optionalStart() + .appendOffsetId() + .optionalEnd() + .toFormatter(); + + @NonNull + public static TimeRange createTimeRange(final @NonNull String fromRaw, final @NonNull String toRaw) throws TimeRangeException { + final String from = fromRaw.trim(); + final String to = toRaw.trim(); + + final boolean fromDateTime = hasIsoDateAndTimeSeparator(from); + final boolean toDateTime = hasIsoDateAndTimeSeparator(to); + if (fromDateTime != toDateTime) { + throw new TimeRangeException("`from` and `to` must both use a full date-time or both use time-of-day only"); + } + + if (fromDateTime) { + return new ExactUTCDateTimeRange(parseInstantUtc(from), parseInstantUtc(to)); + } + + if (looksLikeTimeOfDay(from) && looksLikeTimeOfDay(to)) { + return new OnlyUTCTimeRange(parseUtcLocalTime(from), parseUtcLocalTime(to)); + } + + throw new TimeRangeException("Unrecognized time range format: [%s, %s]".formatted(fromRaw, toRaw)); + } + + private static boolean hasIsoDateAndTimeSeparator(final String s) { + return s.length() >= 11 + && s.charAt(4) == '-' + && s.charAt(7) == '-' + && s.charAt(10) == 'T'; + } + + private static boolean looksLikeTimeOfDay(final String s) { + return s.length() >= 5 + && Character.isDigit(s.charAt(0)) + && Character.isDigit(s.charAt(1)) + && s.charAt(2) == ':' + && Character.isDigit(s.charAt(3)) + && Character.isDigit(s.charAt(4)); + } + + private static @NonNull Instant parseInstantUtc(final String s) { + final TemporalAccessor parsed; + try { + parsed = ISO_LOCAL_DATE_TIME_OPTIONAL_OFFSET.parse(s); + } catch (final DateTimeParseException e) { + throw new TimeRangeException("Invalid date-time: " + s); + } + final LocalDate date = LocalDate.from(parsed); + final LocalTime time = LocalTime.from(parsed); + if (parsed.isSupported(ChronoField.OFFSET_SECONDS)) { + return OffsetDateTime.of(date, time, ZoneOffset.ofTotalSeconds(parsed.get(ChronoField.OFFSET_SECONDS))) + .toInstant(); + } + return LocalDateTime.of(date, time).toInstant(ZoneOffset.UTC); + } + + private static @NonNull LocalTime parseUtcLocalTime(final String s) { + final TemporalAccessor parsed; + try { + parsed = ISO_LOCAL_TIME_OPTIONAL_OFFSET.parse(s); + } catch (final DateTimeParseException e) { + throw new TimeRangeException("Invalid time-of-day: " + s); + } + final LocalTime local = LocalTime.from(parsed); + if (parsed.isSupported(ChronoField.OFFSET_SECONDS)) { + return OffsetTime.of(local, ZoneOffset.ofTotalSeconds(parsed.get(ChronoField.OFFSET_SECONDS))) + .withOffsetSameInstant(ZoneOffset.UTC) + .toLocalTime(); + } + return local; + } +} From 2147b2f0a570772f0f68f37b5ca8ac51ccd17f5f Mon Sep 17 00:00:00 2001 From: av Date: Sun, 10 May 2026 01:07:30 +0300 Subject: [PATCH 2/8] improved: introduced MOTD Repository as a SSoT for providing MOTDs; created cohesive MOTDSettings for storing config and repository together --- .../common/config/MOTDRepository.java | 74 +++++++++++++++++++ .../minimotd/common/config/MOTDSettings.java | 21 ++++++ 2 files changed, 95 insertions(+) create mode 100644 common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDRepository.java create mode 100644 common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDSettings.java diff --git a/common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDRepository.java b/common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDRepository.java new file mode 100644 index 00000000..2f91bc58 --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDRepository.java @@ -0,0 +1,74 @@ +package xyz.jpenilla.minimotd.common.config; + +import java.util.List; +import org.jspecify.annotations.NonNull; +import xyz.jpenilla.minimotd.common.model.MOTD; +import xyz.jpenilla.minimotd.common.model.timerange.TimeRange; +import xyz.jpenilla.minimotd.common.model.timerange.TimeRangeFactory; + +public final class MOTDRepository { + private final List motds; + + public MOTDRepository(final @NonNull List motds) { + this.motds = motds; + } + + @NonNull + public List motds() { + return motds; + } + + + @NonNull + static MOTDRepository fromConfig(final MOTDConfig config) { + if (config.motds().isEmpty()) { + return new MOTDRepository(List.of()); + } + + return new MOTDRepository(config.motds().stream() + .map(MOTDAdapter::new) + .toList()); + } + + private static final class MOTDAdapter implements MOTD { + private final MOTDConfig.MOTD delegate; + private final List timeRanges; + + public MOTDAdapter(MOTDConfig.MOTD delegate) { + this.delegate = delegate; + this.timeRanges = delegate.getScheduleSettings().getTimeSchedule().stream() + .map(timeRangeConfig -> TimeRangeFactory.createTimeRange(timeRangeConfig.getFrom(), timeRangeConfig.getTo())) + .toList(); + } + + @Override + public @NonNull String line1() { + return delegate.line1(); + } + + @Override + public @NonNull String line2() { + return delegate.line2(); + } + + @Override + public @NonNull String icon() { + return delegate.icon(); + } + + @Override + public @NonNull List timeRanges() { + return timeRanges; + } + + @Override + public int weight() { + return delegate.getScheduleSettings().getWeight(); + } + + @Override + public int priority() { + return delegate.getScheduleSettings().getPriority(); + } + } +} diff --git a/common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDSettings.java b/common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDSettings.java new file mode 100644 index 00000000..55d63355 --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDSettings.java @@ -0,0 +1,21 @@ +package xyz.jpenilla.minimotd.common.config; + +import org.jspecify.annotations.NonNull; + +public final class MOTDSettings { + private final MOTDConfig motdConfig; + private final MOTDRepository motdRepository; + + public MOTDSettings(final @NonNull MOTDConfig motdConfig) { + this.motdConfig = motdConfig; + this.motdRepository = MOTDRepository.fromConfig(motdConfig); + } + + public @NonNull MOTDConfig getMotdConfig() { + return motdConfig; + } + + public @NonNull MOTDRepository getMotdRepository() { + return motdRepository; + } +} From ae0842f43ce086502a354fa8cf7d2a35d443626f Mon Sep 17 00:00:00 2001 From: av Date: Sun, 10 May 2026 01:07:58 +0300 Subject: [PATCH 3/8] improved: implemented MOTD selection logic --- .../minimotd/common/util/MOTDSelector.java | 84 ++++++++++ .../common/util/MOTDSelectorTest.java | 153 ++++++++++++++++++ 2 files changed, 237 insertions(+) create mode 100644 common/src/main/java/xyz/jpenilla/minimotd/common/util/MOTDSelector.java create mode 100644 common/src/test/java/xyz/jpenilla/minimotd/common/util/MOTDSelectorTest.java diff --git a/common/src/main/java/xyz/jpenilla/minimotd/common/util/MOTDSelector.java b/common/src/main/java/xyz/jpenilla/minimotd/common/util/MOTDSelector.java new file mode 100644 index 00000000..1ebc46d2 --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/minimotd/common/util/MOTDSelector.java @@ -0,0 +1,84 @@ +package xyz.jpenilla.minimotd.common.util; + +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NonNull; +import xyz.jpenilla.minimotd.common.config.MOTDConfig; +import xyz.jpenilla.minimotd.common.model.MOTD; + +public final class MOTDSelector { + private static List filterInTimeRange(final List motds) { + final OffsetDateTime now = OffsetDateTime.now(ZoneId.of("UTC")); + + final List motdsInCurrentTimeRange = motds.stream() + .filter(motd -> motd.timeRanges().isEmpty() + || motd.timeRanges().stream() + .anyMatch(timeRange -> timeRange.isInTimeRange(now))) + .toList(); + + if (motdsInCurrentTimeRange.isEmpty()) { + throw new IllegalStateException("MOTD is enabled, but there are no MOTDs for current time range. Probably you should define at least one default"); + } + + return motdsInCurrentTimeRange; + } + + private static List filterByPriority(final List motds) { + if (motds.size() <= 1) { + return motds; + } + + final int highestPriority = motds.stream() + .mapToInt(MOTD::priority) + .min() + .getAsInt(); + + return motds.stream() + .filter(motd -> motd.priority() == highestPriority) + .toList(); + } + + private static MOTD selectByWeight(final List motds) { + if (motds.isEmpty()) { + throw new IllegalArgumentException("MOTDs expected to be not empty"); + } + + if (motds.size() == 1) { + return motds.get(0); + } + + final long weightSum = motds.stream() + .mapToLong(MOTD::weight) + .sum(); + + long stopWeightCount = ThreadLocalRandom.current().nextLong(weightSum); + + for (final MOTD motd : motds) { + stopWeightCount -= motd.weight(); + if (stopWeightCount <= 0) { + return motd; + } + } + + // Fallback, but expected to be not reachable + return motds.stream().max(Comparator.comparing(MOTD::weight)).orElseThrow(); + } + + + public static @NonNull MOTD select(final @NotNull List motds) { + if (motds.isEmpty()) { + throw new IllegalArgumentException("MOTDs expected to be not empty"); + } + + final var inTimeRange = filterInTimeRange(motds); + + final var inPriority = filterByPriority(inTimeRange); + + return selectByWeight(inPriority); + } +} diff --git a/common/src/test/java/xyz/jpenilla/minimotd/common/util/MOTDSelectorTest.java b/common/src/test/java/xyz/jpenilla/minimotd/common/util/MOTDSelectorTest.java new file mode 100644 index 00000000..af035c9a --- /dev/null +++ b/common/src/test/java/xyz/jpenilla/minimotd/common/util/MOTDSelectorTest.java @@ -0,0 +1,153 @@ +/* + * This file is part of MiniMOTD, licensed under the MIT License. + * + * Copyright (c) 2020-2025 Jason Penilla + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package xyz.jpenilla.minimotd.common.util; + +import java.util.List; +import org.jspecify.annotations.NonNull; +import org.junit.jupiter.api.Test; +import xyz.jpenilla.minimotd.common.model.MOTD; +import xyz.jpenilla.minimotd.common.model.timerange.TimeRange; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class MOTDSelectorTest { + private static final TimeRange ALWAYS = now -> true; + private static final TimeRange NEVER = now -> false; + + @Test + void select_emptyList_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> MOTDSelector.select(List.of())); + } + + @Test + void select_allOutOfTimeRange_throwsIllegalStateException() { + final List motds = List.of( + motd("a", List.of(NEVER), 1, 0), + motd("b", List.of(NEVER), 1, 0) + ); + final IllegalStateException ex = assertThrows(IllegalStateException.class, () -> MOTDSelector.select(motds)); + assertTrue(ex.getMessage().contains("no MOTDs for current time range")); + } + + @Test + void select_singleMotdWithNoTimeRanges_returnsIt() { + final TestMOTD only = motd("only", List.of(), 1, 0); + assertSame(only, MOTDSelector.select(List.of(only))); + } + + @Test + void select_distinctPriorities_keepsLowestNumericPriority() { + final TestMOTD best = motd("best", List.of(), 1, 1); + final List motds = List.of( + motd("mid", List.of(), 1, 5), + best, + motd("low", List.of(), 1, 10) + ); + assertSame(best, MOTDSelector.select(motds)); + } + + @Test + void select_oneActiveOneInactive_returnsActive() { + final TestMOTD active = motd("active", List.of(), 1, 0); + final List motds = List.of( + active, + motd("inactive", List.of(NEVER), 1, 0) + ); + assertSame(active, MOTDSelector.select(motds)); + } + + @Test + void select_anyTimeRangeMatch_isActive() { + final TestMOTD orRanges = motd("or", List.of(NEVER, ALWAYS), 1, 0); + assertSame(orRanges, MOTDSelector.select(List.of(orRanges))); + } + + @Test + void select_samePriorityAfterFilters_returnsOneOfCandidates() { + final TestMOTD first = motd("first", List.of(), 1, 0); + final TestMOTD second = motd("second", List.of(), 1, 0); + final MOTD result = MOTDSelector.select(List.of(first, second)); + assertTrue(result == first || result == second); + } + + private static TestMOTD motd( + final String id, + final List timeRanges, + final int weight, + final int priority + ) { + return new TestMOTD(id, timeRanges, weight, priority); + } + + private static final class TestMOTD implements MOTD { + private final String id; + private final List timeRanges; + private final int weight; + private final int priority; + + private TestMOTD( + final String id, + final List timeRanges, + final int weight, + final int priority + ) { + this.id = id; + this.timeRanges = List.copyOf(timeRanges); + this.weight = weight; + this.priority = priority; + } + + @Override + public @NonNull String line1() { + return this.id; + } + + @Override + public @NonNull String line2() { + return ""; + } + + @Override + public @NonNull String icon() { + return ""; + } + + @Override + public @NonNull List timeRanges() { + return this.timeRanges; + } + + @Override + public int weight() { + return this.weight; + } + + @Override + public int priority() { + return this.priority; + } + } +} From 62c70fc4396443402237a13aa8368163e16e80af Mon Sep 17 00:00:00 2001 From: av Date: Sun, 10 May 2026 01:09:09 +0300 Subject: [PATCH 4/8] refactored: imports cleanup --- .../java/xyz/jpenilla/minimotd/common/util/MOTDSelector.java | 2 -- .../xyz/jpenilla/minimotd/common/util/MOTDSelectorTest.java | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/common/src/main/java/xyz/jpenilla/minimotd/common/util/MOTDSelector.java b/common/src/main/java/xyz/jpenilla/minimotd/common/util/MOTDSelector.java index 1ebc46d2..34408a82 100644 --- a/common/src/main/java/xyz/jpenilla/minimotd/common/util/MOTDSelector.java +++ b/common/src/main/java/xyz/jpenilla/minimotd/common/util/MOTDSelector.java @@ -1,6 +1,5 @@ package xyz.jpenilla.minimotd.common.util; -import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.ZoneId; import java.util.Comparator; @@ -8,7 +7,6 @@ import java.util.concurrent.ThreadLocalRandom; import org.jetbrains.annotations.NotNull; import org.jspecify.annotations.NonNull; -import xyz.jpenilla.minimotd.common.config.MOTDConfig; import xyz.jpenilla.minimotd.common.model.MOTD; public final class MOTDSelector { diff --git a/common/src/test/java/xyz/jpenilla/minimotd/common/util/MOTDSelectorTest.java b/common/src/test/java/xyz/jpenilla/minimotd/common/util/MOTDSelectorTest.java index af035c9a..88e12155 100644 --- a/common/src/test/java/xyz/jpenilla/minimotd/common/util/MOTDSelectorTest.java +++ b/common/src/test/java/xyz/jpenilla/minimotd/common/util/MOTDSelectorTest.java @@ -29,9 +29,7 @@ import xyz.jpenilla.minimotd.common.model.MOTD; import xyz.jpenilla.minimotd.common.model.timerange.TimeRange; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; class MOTDSelectorTest { private static final TimeRange ALWAYS = now -> true; From 451bf123903fb4fc167e7139e853c2e1d6c45f21 Mon Sep 17 00:00:00 2001 From: av Date: Sun, 10 May 2026 01:10:08 +0300 Subject: [PATCH 5/8] refactored: config manager main entity is MOTDSettings instead of MOTDConfig only --- .../jpenilla/minimotd/common/MiniMOTD.java | 26 +++++---- .../minimotd/common/config/ConfigManager.java | 40 ++++++-------- .../minimotd/common/config/MOTDConfig.java | 55 ++++++++++++++++++- .../minimotd/bukkit/PaperPingListener.java | 6 +- .../minimotd/bukkit/PingListener.java | 6 +- .../minimotd/bungee/MiniMOTDBungee.java | 6 +- .../minimotd/bungee/PingListener.java | 6 +- .../ServerStatusPacketListenerImplMixin.java | 6 +- .../ServerStatusPacketListenerImplMixin.java | 6 +- .../jpenilla/minimotd/paper/PingListener.java | 6 +- .../ClientPingServerEventListener.java | 6 +- .../ClientPingServerEventListener.java | 6 +- .../minimotd/velocity/PingListener.java | 8 +-- 13 files changed, 118 insertions(+), 65 deletions(-) diff --git a/common/src/main/java/xyz/jpenilla/minimotd/common/MiniMOTD.java b/common/src/main/java/xyz/jpenilla/minimotd/common/MiniMOTD.java index e4877b4b..a383a9db 100644 --- a/common/src/main/java/xyz/jpenilla/minimotd/common/MiniMOTD.java +++ b/common/src/main/java/xyz/jpenilla/minimotd/common/MiniMOTD.java @@ -24,7 +24,6 @@ package xyz.jpenilla.minimotd.common; import java.nio.file.Path; -import java.util.concurrent.ThreadLocalRandom; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; @@ -33,6 +32,10 @@ import org.slf4j.Logger; import xyz.jpenilla.minimotd.common.config.ConfigManager; import xyz.jpenilla.minimotd.common.config.MOTDConfig; +import xyz.jpenilla.minimotd.common.config.MOTDRepository; +import xyz.jpenilla.minimotd.common.config.MOTDSettings; +import xyz.jpenilla.minimotd.common.model.MOTD; +import xyz.jpenilla.minimotd.common.util.MOTDSelector; import xyz.jpenilla.minimotd.common.util.MiniPlaceholdersUtil; import static net.kyori.adventure.text.Component.newline; @@ -75,7 +78,10 @@ public ConfigManager configManager() { return this.configManager; } - public PingResponse createMOTD(final MOTDConfig config, final int onlinePlayers, final int maxPlayers) { + public PingResponse createMOTD(final MOTDSettings motdSettings, final int onlinePlayers, final int maxPlayers) { + final MOTDConfig config = motdSettings.getMotdConfig(); + final MOTDRepository motdRepository = motdSettings.getMotdRepository(); + final PingResponse.PlayerCount count = config.modifyPlayerCount(onlinePlayers, maxPlayers); final PingResponse.Builder response = PingResponse.builder() .playerCount(count) @@ -84,18 +90,18 @@ public PingResponse createMOTD(final MOTDConfig config, final int onlinePlaye String iconString = null; if (config.motdEnabled()) { - if (config.motds().isEmpty()) { + if (motdRepository.motds().isEmpty()) { throw new IllegalStateException("MOTD is enabled, but there are no MOTDs in the config file?"); } - final int index = config.motds().size() == 1 ? 0 : ThreadLocalRandom.current().nextInt(config.motds().size()); - final MOTDConfig.MOTD motdConfig = config.motds().get(index); - final Component motd = Component.textOfChildren( - parse(motdConfig.line1(), count), + + final MOTD motd = MOTDSelector.select(motdRepository.motds()); + final Component motdComponent = Component.textOfChildren( + parse(motd.line1(), count), newline(), - parse(motdConfig.line2(), count) + parse(motd.line2(), count) ); - response.motd(motd); - iconString = motdConfig.icon(); + response.motd(motdComponent); + iconString = motd.icon(); } if (config.iconEnabled()) { diff --git a/common/src/main/java/xyz/jpenilla/minimotd/common/config/ConfigManager.java b/common/src/main/java/xyz/jpenilla/minimotd/common/config/ConfigManager.java index 0b22d7b7..b2ce03b2 100644 --- a/common/src/main/java/xyz/jpenilla/minimotd/common/config/ConfigManager.java +++ b/common/src/main/java/xyz/jpenilla/minimotd/common/config/ConfigManager.java @@ -27,11 +27,7 @@ import java.net.InetSocketAddress; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; import org.jspecify.annotations.NonNull; @@ -47,12 +43,12 @@ public final class ConfigManager { private final MiniMOTD miniMOTD; private final ConfigLoader mainConfigLoader; - private MOTDConfig mainConfig; + private MOTDSettings motdSettings; private final ConfigLoader pluginSettingsLoader; private PluginSettings pluginSettings; - private final Map extraConfigs = new HashMap<>(); + private final Map extraConfigs = new HashMap<>(); public ConfigManager(final @NonNull MiniMOTD miniMOTD) { this.miniMOTD = miniMOTD; @@ -70,8 +66,8 @@ public ConfigManager(final @NonNull MiniMOTD miniMOTD) { public void loadConfigs() { try { - this.mainConfig = this.mainConfigLoader.load(); - this.mainConfigLoader.save(this.mainConfig); + this.motdSettings = new MOTDSettings(this.mainConfigLoader.load()); + this.mainConfigLoader.save(motdSettings.getMotdConfig()); this.pluginSettings = this.pluginSettingsLoader.load(); this.pluginSettingsLoader.save(this.pluginSettings); @@ -99,9 +95,9 @@ public void loadExtraConfigs() { path, options -> options.header(String.format("Extra MiniMOTD config '%s'", name)) ); - final MOTDConfig config = loader.load(); - loader.save(config); - this.extraConfigs.put(name, config); + final MOTDSettings motdSettings = new MOTDSettings(loader.load()); + loader.save(motdSettings.getMotdConfig()); + this.extraConfigs.put(name, motdSettings); } } } catch (final IOException e) { @@ -123,11 +119,11 @@ private void createDefaultExtraConfigs(final @NonNull Path extraConfigsDir) thro } } - public @NonNull MOTDConfig mainConfig() { - if (this.mainConfig == null) { + public @NonNull MOTDSettings motdSettings() { + if (this.motdSettings == null) { throw new IllegalStateException("Config has not yet been loaded"); } - return this.mainConfig; + return this.motdSettings; } public @NonNull PluginSettings pluginSettings() { @@ -137,9 +133,9 @@ private void createDefaultExtraConfigs(final @NonNull Path extraConfigsDir) thro return this.pluginSettings; } - public @NonNull MOTDConfig resolveConfig(final @Nullable InetSocketAddress address) { + public @NonNull MOTDSettings resolveConfig(final @Nullable InetSocketAddress address) { if (address == null) { - return this.mainConfig(); + return this.motdSettings(); } final String configString = this.pluginSettings().proxySettings().findConfigStringForHost(address.getHostString(), address.getPort()); @@ -148,21 +144,21 @@ private void createDefaultExtraConfigs(final @NonNull Path extraConfigsDir) thro } if (configString == null) { - return this.mainConfig(); + return this.motdSettings(); } return this.resolveConfig(configString); } - public @NonNull MOTDConfig resolveConfig(final @NonNull String name) { + public @NonNull MOTDSettings resolveConfig(final @NonNull String name) { if ("default".equals(name)) { - return this.mainConfig(); + return this.motdSettings(); } - final MOTDConfig cfg = this.extraConfigs.get(name); + final MOTDSettings cfg = this.extraConfigs.get(name); if (cfg != null) { return cfg; } this.miniMOTD.logger().warn("Invalid extra-config name: '{}', falling back to main.conf", name); - return this.mainConfig(); + return this.motdSettings(); } } diff --git a/common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDConfig.java b/common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDConfig.java index a4f49688..633486fc 100644 --- a/common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDConfig.java +++ b/common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDConfig.java @@ -29,6 +29,7 @@ import org.jspecify.annotations.NonNull; import org.spongepowered.configurate.objectmapping.ConfigSerializable; import org.spongepowered.configurate.objectmapping.meta.Comment; +import org.spongepowered.configurate.objectmapping.meta.Matches; import xyz.jpenilla.minimotd.common.PingResponse; import static xyz.jpenilla.minimotd.common.PingResponse.PlayerCount.playerCount; @@ -62,7 +63,52 @@ public MOTDConfig(final @NonNull MOTD @NonNull ... defaults) { private PlayerCountSettings playerCountSettings = new PlayerCountSettings(); @ConfigSerializable - public static final class MOTD { + public static final class MOTDScheduleSettings { + @Comment("Weight defines probability to be chosen on conflict.\n" + + " Higher value - Higher probability") + @Matches("[1-9][0-9]{0-5}") + private int weight = 1; + + @Comment("MOTDs with lower priority will be ignored on conflict.\n" + + " Lower value - Higher priority.\n" + + " If few MOTDs have same priority then they will randomly compete based on `weight`") + @Matches("[1-9][0-9]{0-5}") + private int priority = 1; + + @Comment("Time ranges when MOTD should be shown\n" + + " If none set - then could be shown at any time\n" + + " Time-zone is UTC") + private List timeSchedule = List.of(); + + public int getWeight() { + return weight; + } + + public int getPriority() { + return priority; + } + + public List getTimeSchedule() { + return timeSchedule; + } + } + + @ConfigSerializable + public static final class TimeRangeConfig { + private String from; + private String to; + + public String getFrom() { + return from; + } + + public String getTo() { + return to; + } + } + + @ConfigSerializable + static final class MOTD { public MOTD() { } @@ -82,6 +128,8 @@ public MOTD(final @NonNull String line1, final @NonNull String line2) { + " ex: icon=\"myIconFile\"") private String icon = "random"; + private MOTDScheduleSettings scheduleSettings = new MOTDScheduleSettings(); + public @NonNull String line1() { return this.line1; } @@ -94,6 +142,9 @@ public MOTD(final @NonNull String line1, final @NonNull String line2) { return this.icon; } + public @NonNull MOTDScheduleSettings getScheduleSettings() { + return scheduleSettings; + } } @ConfigSerializable @@ -170,7 +221,7 @@ public boolean iconEnabled() { return this.iconEnabled; } - public @NonNull List motds() { + @NonNull List motds() { return this.motds; } diff --git a/platform/bukkit/src/main/java/xyz/jpenilla/minimotd/bukkit/PaperPingListener.java b/platform/bukkit/src/main/java/xyz/jpenilla/minimotd/bukkit/PaperPingListener.java index 43a74480..29a97438 100644 --- a/platform/bukkit/src/main/java/xyz/jpenilla/minimotd/bukkit/PaperPingListener.java +++ b/platform/bukkit/src/main/java/xyz/jpenilla/minimotd/bukkit/PaperPingListener.java @@ -36,7 +36,7 @@ import xyz.jpenilla.minimotd.common.Constants; import xyz.jpenilla.minimotd.common.MiniMOTD; import xyz.jpenilla.minimotd.common.PingResponse; -import xyz.jpenilla.minimotd.common.config.MOTDConfig; +import xyz.jpenilla.minimotd.common.config.MOTDSettings; public final class PaperPingListener implements Listener { private static final Method WORK_AROUND_PAPER_BUG; @@ -60,9 +60,9 @@ public final class PaperPingListener implements Listener { @EventHandler public void handlePing(final @NonNull PaperServerListPingEvent event) { - final MOTDConfig cfg = this.miniMOTD.configManager().mainConfig(); + final MOTDSettings motdSettings = this.miniMOTD.configManager().motdSettings(); - final PingResponse response = this.miniMOTD.createMOTD(cfg, event.getNumPlayers(), event.getMaxPlayers()); + final PingResponse response = this.miniMOTD.createMOTD(motdSettings, event.getNumPlayers(), event.getMaxPlayers()); response.playerCount().applyCount(event::setNumPlayers, event::setMaxPlayers); response.motd(motd -> { diff --git a/platform/bukkit/src/main/java/xyz/jpenilla/minimotd/bukkit/PingListener.java b/platform/bukkit/src/main/java/xyz/jpenilla/minimotd/bukkit/PingListener.java index ae578468..9f147592 100644 --- a/platform/bukkit/src/main/java/xyz/jpenilla/minimotd/bukkit/PingListener.java +++ b/platform/bukkit/src/main/java/xyz/jpenilla/minimotd/bukkit/PingListener.java @@ -32,7 +32,7 @@ import org.jspecify.annotations.NonNull; import xyz.jpenilla.minimotd.common.MiniMOTD; import xyz.jpenilla.minimotd.common.PingResponse; -import xyz.jpenilla.minimotd.common.config.MOTDConfig; +import xyz.jpenilla.minimotd.common.config.MOTDSettings; public final class PingListener implements Listener { private final MiniMOTD miniMOTD; @@ -44,9 +44,9 @@ public final class PingListener implements Listener { @EventHandler public void handlePing(final @NonNull ServerListPingEvent event) { - final MOTDConfig cfg = this.miniMOTD.configManager().mainConfig(); + final MOTDSettings motdSettings = this.miniMOTD.configManager().motdSettings(); - final PingResponse response = this.miniMOTD.createMOTD(cfg, event.getNumPlayers(), event.getMaxPlayers()); + final PingResponse response = this.miniMOTD.createMOTD(motdSettings, event.getNumPlayers(), event.getMaxPlayers()); event.setMaxPlayers(response.playerCount().maxPlayers()); response.motd(motd -> { diff --git a/platform/bungeecord/src/main/java/xyz/jpenilla/minimotd/bungee/MiniMOTDBungee.java b/platform/bungeecord/src/main/java/xyz/jpenilla/minimotd/bungee/MiniMOTDBungee.java index 03a6abfc..0e553895 100644 --- a/platform/bungeecord/src/main/java/xyz/jpenilla/minimotd/bungee/MiniMOTDBungee.java +++ b/platform/bungeecord/src/main/java/xyz/jpenilla/minimotd/bungee/MiniMOTDBungee.java @@ -40,7 +40,7 @@ import xyz.jpenilla.minimotd.common.MiniMOTD; import xyz.jpenilla.minimotd.common.MiniMOTDPlatform; import xyz.jpenilla.minimotd.common.PingResponse; -import xyz.jpenilla.minimotd.common.config.MOTDConfig; +import xyz.jpenilla.minimotd.common.config.MOTDSettings; import xyz.jpenilla.minimotd.common.util.UpdateChecker; public final class MiniMOTDBungee extends Plugin implements MiniMOTDPlatform { @@ -116,8 +116,8 @@ private void injectTravertineGson() { } private void preload() { - final MOTDConfig cfg = this.miniMOTD.configManager().mainConfig(); - final PingResponse mini = this.miniMOTD.createMOTD(cfg, 0, 0); + final MOTDSettings motdSettings = this.miniMOTD.configManager().motdSettings(); + final PingResponse mini = this.miniMOTD.createMOTD(motdSettings, 0, 0); mini.motd(motd -> BungeeComponentSerializer.get().serialize(motd)); } } diff --git a/platform/bungeecord/src/main/java/xyz/jpenilla/minimotd/bungee/PingListener.java b/platform/bungeecord/src/main/java/xyz/jpenilla/minimotd/bungee/PingListener.java index b8e7bec6..a133a367 100644 --- a/platform/bungeecord/src/main/java/xyz/jpenilla/minimotd/bungee/PingListener.java +++ b/platform/bungeecord/src/main/java/xyz/jpenilla/minimotd/bungee/PingListener.java @@ -35,7 +35,7 @@ import xyz.jpenilla.minimotd.common.Constants; import xyz.jpenilla.minimotd.common.MiniMOTD; import xyz.jpenilla.minimotd.common.PingResponse; -import xyz.jpenilla.minimotd.common.config.MOTDConfig; +import xyz.jpenilla.minimotd.common.config.MOTDSettings; public final class PingListener implements Listener { private final MiniMOTD miniMOTD; @@ -52,8 +52,8 @@ public void onPing(final @NonNull ProxyPingEvent e) { } final ServerPing.Players players = response.getPlayers(); - final MOTDConfig cfg = this.miniMOTD.configManager().resolveConfig(e.getConnection().getVirtualHost()); - final PingResponse mini = this.miniMOTD.createMOTD(cfg, players.getOnline(), players.getMax()); + final MOTDSettings motdSettings = this.miniMOTD.configManager().resolveConfig(e.getConnection().getVirtualHost()); + final PingResponse mini = this.miniMOTD.createMOTD(motdSettings, players.getOnline(), players.getMax()); if (mini.hidePlayerCount()) { response.setPlayers(null); diff --git a/platform/fabric/src/main/java/xyz/jpenilla/minimotd/fabric/mixin/ServerStatusPacketListenerImplMixin.java b/platform/fabric/src/main/java/xyz/jpenilla/minimotd/fabric/mixin/ServerStatusPacketListenerImplMixin.java index 8f58f9fe..5d6cf85a 100644 --- a/platform/fabric/src/main/java/xyz/jpenilla/minimotd/fabric/mixin/ServerStatusPacketListenerImplMixin.java +++ b/platform/fabric/src/main/java/xyz/jpenilla/minimotd/fabric/mixin/ServerStatusPacketListenerImplMixin.java @@ -38,7 +38,7 @@ import xyz.jpenilla.minimotd.common.Constants; import xyz.jpenilla.minimotd.common.MiniMOTD; import xyz.jpenilla.minimotd.common.PingResponse; -import xyz.jpenilla.minimotd.common.config.MOTDConfig; +import xyz.jpenilla.minimotd.common.config.MOTDSettings; import xyz.jpenilla.minimotd.common.util.ComponentColorDownsampler; import xyz.jpenilla.minimotd.fabric.MiniMOTDFabric; import xyz.jpenilla.minimotd.fabric.access.ConnectionAccess; @@ -58,10 +58,10 @@ public ServerStatus injectHandleStatusRequest(final ServerStatusPacketListenerIm final ServerStatus vanillaStatus = Objects.requireNonNull(minecraftServer.getStatus(), "vanillaStatus"); final MiniMOTD miniMOTD = miniMOTDFabric.miniMOTD(); - final MOTDConfig config = miniMOTD.configManager().mainConfig(); + final MOTDSettings motdSettings = miniMOTD.configManager().motdSettings(); final PingResponse response = miniMOTD.createMOTD( - config, + motdSettings, minecraftServer.getPlayerCount(), vanillaStatus.players().map(ServerStatus.Players::max).orElseGet(minecraftServer::getMaxPlayers) ); diff --git a/platform/neoforge/src/main/java/xyz/jpenilla/minimotd/neoforge/mixin/ServerStatusPacketListenerImplMixin.java b/platform/neoforge/src/main/java/xyz/jpenilla/minimotd/neoforge/mixin/ServerStatusPacketListenerImplMixin.java index b7c081d4..313b1c88 100644 --- a/platform/neoforge/src/main/java/xyz/jpenilla/minimotd/neoforge/mixin/ServerStatusPacketListenerImplMixin.java +++ b/platform/neoforge/src/main/java/xyz/jpenilla/minimotd/neoforge/mixin/ServerStatusPacketListenerImplMixin.java @@ -38,7 +38,7 @@ import xyz.jpenilla.minimotd.common.Constants; import xyz.jpenilla.minimotd.common.MiniMOTD; import xyz.jpenilla.minimotd.common.PingResponse; -import xyz.jpenilla.minimotd.common.config.MOTDConfig; +import xyz.jpenilla.minimotd.common.config.MOTDSettings; import xyz.jpenilla.minimotd.common.util.ComponentColorDownsampler; import xyz.jpenilla.minimotd.neoforge.MiniMOTDNeoForge; import xyz.jpenilla.minimotd.neoforge.access.ConnectionAccess; @@ -58,10 +58,10 @@ public ServerStatus injectHandleStatusRequest(final ServerStatusPacketListenerIm final ServerStatus vanillaStatus = Objects.requireNonNull(minecraftServer.getStatus(), "vanillaStatus"); final MiniMOTD miniMOTD = miniMOTDNeoForge.miniMOTD(); - final MOTDConfig config = miniMOTD.configManager().mainConfig(); + final MOTDSettings motdSettings = miniMOTD.configManager().motdSettings(); final PingResponse response = miniMOTD.createMOTD( - config, + motdSettings, minecraftServer.getPlayerCount(), vanillaStatus.players().map(ServerStatus.Players::max).orElseGet(minecraftServer::getMaxPlayers) ); diff --git a/platform/paper/src/main/java/xyz/jpenilla/minimotd/paper/PingListener.java b/platform/paper/src/main/java/xyz/jpenilla/minimotd/paper/PingListener.java index 9d0b523c..1a98c1d3 100644 --- a/platform/paper/src/main/java/xyz/jpenilla/minimotd/paper/PingListener.java +++ b/platform/paper/src/main/java/xyz/jpenilla/minimotd/paper/PingListener.java @@ -31,7 +31,7 @@ import xyz.jpenilla.minimotd.common.Constants; import xyz.jpenilla.minimotd.common.MiniMOTD; import xyz.jpenilla.minimotd.common.PingResponse; -import xyz.jpenilla.minimotd.common.config.MOTDConfig; +import xyz.jpenilla.minimotd.common.config.MOTDSettings; import xyz.jpenilla.minimotd.common.util.ComponentColorDownsampler; public final class PingListener implements Listener { @@ -43,9 +43,9 @@ public final class PingListener implements Listener { @EventHandler public void handlePing(final @NonNull PaperServerListPingEvent event) { - final MOTDConfig cfg = this.miniMOTD.configManager().mainConfig(); + final MOTDSettings motdSettings = this.miniMOTD.configManager().motdSettings(); - final PingResponse response = this.miniMOTD.createMOTD(cfg, event.getNumPlayers(), event.getMaxPlayers()); + final PingResponse response = this.miniMOTD.createMOTD(motdSettings, event.getNumPlayers(), event.getMaxPlayers()); response.playerCount().applyCount(event::setNumPlayers, event::setMaxPlayers); response.motd(motd -> { diff --git a/platform/sponge7/src/main/java/xyz/jpenilla/minimotd/sponge7/ClientPingServerEventListener.java b/platform/sponge7/src/main/java/xyz/jpenilla/minimotd/sponge7/ClientPingServerEventListener.java index 9f31c222..a2a8c947 100644 --- a/platform/sponge7/src/main/java/xyz/jpenilla/minimotd/sponge7/ClientPingServerEventListener.java +++ b/platform/sponge7/src/main/java/xyz/jpenilla/minimotd/sponge7/ClientPingServerEventListener.java @@ -31,7 +31,7 @@ import org.spongepowered.api.network.status.Favicon; import xyz.jpenilla.minimotd.common.MiniMOTD; import xyz.jpenilla.minimotd.common.PingResponse; -import xyz.jpenilla.minimotd.common.config.MOTDConfig; +import xyz.jpenilla.minimotd.common.config.MOTDSettings; final class ClientPingServerEventListener implements EventListener { private final MiniMOTD miniMOTD; @@ -58,9 +58,9 @@ public void handle(final @NonNull ClientPingServerEvent event) { } } - final MOTDConfig config = this.miniMOTD.configManager().mainConfig(); + final MOTDSettings motdSettings = this.miniMOTD.configManager().motdSettings(); - final PingResponse mini = this.miniMOTD.createMOTD(config, players.getOnline(), players.getMax()); + final PingResponse mini = this.miniMOTD.createMOTD(motdSettings, players.getOnline(), players.getMax()); mini.playerCount().applyCount(players::setOnline, players::setMax); mini.motd(motd -> response.setDescription(SpongeComponentSerializer.get().serialize(motd))); mini.icon(response::setFavicon); diff --git a/platform/sponge8/src/main/java/xyz/jpenilla/minimotd/sponge8/ClientPingServerEventListener.java b/platform/sponge8/src/main/java/xyz/jpenilla/minimotd/sponge8/ClientPingServerEventListener.java index 88db2d49..7294d4a7 100644 --- a/platform/sponge8/src/main/java/xyz/jpenilla/minimotd/sponge8/ClientPingServerEventListener.java +++ b/platform/sponge8/src/main/java/xyz/jpenilla/minimotd/sponge8/ClientPingServerEventListener.java @@ -33,7 +33,7 @@ import xyz.jpenilla.minimotd.common.Constants; import xyz.jpenilla.minimotd.common.MiniMOTD; import xyz.jpenilla.minimotd.common.PingResponse; -import xyz.jpenilla.minimotd.common.config.MOTDConfig; +import xyz.jpenilla.minimotd.common.config.MOTDSettings; import xyz.jpenilla.minimotd.common.util.ComponentColorDownsampler; final class ClientPingServerEventListener implements EventListener { @@ -72,9 +72,9 @@ public void handle(final @NonNull ClientPingServerEvent event) { } } - final MOTDConfig config = this.miniMOTD.configManager().mainConfig(); + final MOTDSettings motdSettings = this.miniMOTD.configManager().motdSettings(); - final PingResponse mini = this.miniMOTD.createMOTD(config, players.online(), players.max()); + final PingResponse mini = this.miniMOTD.createMOTD(motdSettings, players.online(), players.max()); mini.playerCount().applyCount(players::setOnline, players::setMax); mini.motd(motd -> { if (this.legacy(event.client().version())) { diff --git a/platform/velocity/src/main/java/xyz/jpenilla/minimotd/velocity/PingListener.java b/platform/velocity/src/main/java/xyz/jpenilla/minimotd/velocity/PingListener.java index a32f6188..9c2018eb 100644 --- a/platform/velocity/src/main/java/xyz/jpenilla/minimotd/velocity/PingListener.java +++ b/platform/velocity/src/main/java/xyz/jpenilla/minimotd/velocity/PingListener.java @@ -39,7 +39,7 @@ import org.jspecify.annotations.Nullable; import xyz.jpenilla.minimotd.common.MiniMOTD; import xyz.jpenilla.minimotd.common.PingResponse; -import xyz.jpenilla.minimotd.common.config.MOTDConfig; +import xyz.jpenilla.minimotd.common.config.MOTDSettings; @NullMarked public final class PingListener { @@ -58,10 +58,10 @@ public EventTask onProxyPingEvent(final ProxyPingEvent event) { } private void handle(final ProxyPingEvent event) { - final MOTDConfig config = this.miniMOTD.configManager().resolveConfig(event.getConnection().getVirtualHost().orElse(null)); + final MOTDSettings motdSettings = this.miniMOTD.configManager().resolveConfig(event.getConnection().getVirtualHost().orElse(null)); final ServerPing.Builder pong = event.getPing().asBuilder(); - final List targetServers = config.targetServers(); + final List targetServers = motdSettings.getMotdConfig().targetServers(); int playersCount = 0; if (targetServers.isEmpty()) { playersCount = pong.getOnlinePlayers(); @@ -80,7 +80,7 @@ private void handle(final ProxyPingEvent event) { pong.samplePlayers(players.toArray(new ServerPing.SamplePlayer[0])); } - final PingResponse response = this.miniMOTD.createMOTD(config, playersCount, pong.getMaximumPlayers()); + final PingResponse response = this.miniMOTD.createMOTD(motdSettings, playersCount, pong.getMaximumPlayers()); response.icon(pong::favicon); response.motd(pong::description); response.playerCount().applyCount(pong::onlinePlayers, pong::maximumPlayers); From 4f2b6ebbf0afb619913bcc56820e49a0c39f938e Mon Sep 17 00:00:00 2001 From: av Date: Sun, 10 May 2026 01:10:52 +0300 Subject: [PATCH 6/8] improved: created integration tests for MOTD parsing and selecting --- .../MOTDTimeRangeConfigIntegrationTest.java | 146 ++++++++++++++++++ .../motd-selector-priority-no-schedule.conf | 18 +++ ...-selector-priority-weight-no-schedule.conf | 18 +++ .../motd-selector-time-and-priority.conf | 21 +++ .../motd-selector-time-wide-priority.conf | 21 +++ .../integration/motd-timerange-exact.conf | 11 ++ .../motd-timerange-invalid-inverted.conf | 11 ++ .../motd-timerange-invalid-mixed.conf | 11 ++ .../integration/motd-timerange-offset.conf | 11 ++ .../integration/motd-timerange-timeofday.conf | 11 ++ 10 files changed, 279 insertions(+) create mode 100644 common/src/test/java/xyz/jpenilla/minimotd/common/config/MOTDTimeRangeConfigIntegrationTest.java create mode 100644 common/src/test/resources/integration/motd-selector-priority-no-schedule.conf create mode 100644 common/src/test/resources/integration/motd-selector-priority-weight-no-schedule.conf create mode 100644 common/src/test/resources/integration/motd-selector-time-and-priority.conf create mode 100644 common/src/test/resources/integration/motd-selector-time-wide-priority.conf create mode 100644 common/src/test/resources/integration/motd-timerange-exact.conf create mode 100644 common/src/test/resources/integration/motd-timerange-invalid-inverted.conf create mode 100644 common/src/test/resources/integration/motd-timerange-invalid-mixed.conf create mode 100644 common/src/test/resources/integration/motd-timerange-offset.conf create mode 100644 common/src/test/resources/integration/motd-timerange-timeofday.conf diff --git a/common/src/test/java/xyz/jpenilla/minimotd/common/config/MOTDTimeRangeConfigIntegrationTest.java b/common/src/test/java/xyz/jpenilla/minimotd/common/config/MOTDTimeRangeConfigIntegrationTest.java new file mode 100644 index 00000000..66a504db --- /dev/null +++ b/common/src/test/java/xyz/jpenilla/minimotd/common/config/MOTDTimeRangeConfigIntegrationTest.java @@ -0,0 +1,146 @@ +/* + * This file is part of MiniMOTD, licensed under the MIT License. + * + * Copyright (c) 2020-2025 Jason Penilla + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package xyz.jpenilla.minimotd.common.config; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.spongepowered.configurate.ConfigurateException; +import xyz.jpenilla.minimotd.common.model.MOTD; +import xyz.jpenilla.minimotd.common.model.timerange.TimeRange; +import xyz.jpenilla.minimotd.common.model.timerange.TimeRangeException; +import xyz.jpenilla.minimotd.common.util.MOTDSelector; + +import static org.junit.jupiter.api.Assertions.*; + +class MOTDTimeRangeConfigIntegrationTest { + + @Test + void exactUtcDateTimeRange_fromHocon(@TempDir final Path tempDir) throws IOException { + final Path file = copyFixture(tempDir, "motd-timerange-exact.conf"); + final MOTD motd = firstMotd(loadMotdConfig(file)); + final TimeRange range = motd.timeRanges().get(0); + + assertTrue(range.isInTimeRange(OffsetDateTime.of(2024, 6, 15, 12, 0, 0, 0, ZoneOffset.UTC))); + assertFalse(range.isInTimeRange(OffsetDateTime.of(2024, 6, 15, 9, 0, 0, 0, ZoneOffset.UTC))); + assertFalse(range.isInTimeRange(OffsetDateTime.of(2024, 6, 15, 19, 0, 0, 0, ZoneOffset.UTC))); + } + + @Test + void timeOfDayOnlyRange_fromHocon(@TempDir final Path tempDir) throws IOException { + final Path file = copyFixture(tempDir, "motd-timerange-timeofday.conf"); + final MOTD motd = firstMotd(loadMotdConfig(file)); + final TimeRange range = motd.timeRanges().get(0); + + assertTrue(range.isInTimeRange(OffsetDateTime.of(2024, 6, 16, 12, 0, 0, 0, ZoneOffset.UTC))); + assertFalse(range.isInTimeRange(OffsetDateTime.of(2024, 6, 16, 3, 0, 0, 0, ZoneOffset.UTC))); + } + + @Test + void explicitOffsetParsedAsUtcInstant_fromHocon(@TempDir final Path tempDir) throws IOException { + final Path file = copyFixture(tempDir, "motd-timerange-offset.conf"); + final MOTD motd = firstMotd(loadMotdConfig(file)); + final TimeRange range = motd.timeRanges().get(0); + + assertTrue(range.isInTimeRange(OffsetDateTime.of(2024, 6, 15, 12, 0, 0, 0, ZoneOffset.UTC))); + assertFalse(range.isInTimeRange(OffsetDateTime.of(2024, 6, 15, 9, 0, 0, 0, ZoneOffset.UTC))); + assertFalse(range.isInTimeRange(OffsetDateTime.of(2024, 6, 15, 19, 0, 0, 0, ZoneOffset.UTC))); + } + + @Test + void mixedDateTimeAndTimeOfDay_throws(@TempDir final Path tempDir) throws IOException { + final Path file = copyFixture(tempDir, "motd-timerange-invalid-mixed.conf"); + final MOTDConfig config = loadMotdConfig(file); + assertThrows(TimeRangeException.class, () -> new MOTDSettings(config)); + } + + @Test + void invertedBounds_throws(@TempDir final Path tempDir) throws IOException { + final Path file = copyFixture(tempDir, "motd-timerange-invalid-inverted.conf"); + final MOTDConfig config = loadMotdConfig(file); + assertThrows(TimeRangeException.class, () -> new MOTDSettings(config)); + } + + @Test + void motdSelector_withoutSchedule_respectsPriorityOverWeight(@TempDir final Path tempDir) throws IOException { + final Path file = copyFixture(tempDir, "motd-selector-priority-no-schedule.conf"); + final var motds = new MOTDSettings(loadMotdConfig(file)).getMotdRepository().motds(); + final MOTD selected = MOTDSelector.select(motds); + assertEquals("lower-priority-wins", selected.line1()); + } + + @Test + void motdSelector_withoutSchedule_samePriority_respectsWeightCompetition(@TempDir final Path tempDir) throws IOException { + final Path file = copyFixture(tempDir, "motd-selector-priority-weight-no-schedule.conf"); + final var motds = new MOTDSettings(loadMotdConfig(file)).getMotdRepository().motds(); + final MOTD selected = MOTDSelector.select(motds); + assertTrue( + "candidate-a".equals(selected.line1()) || "candidate-b".equals(selected.line1()), + "expected one of the equal-priority MOTDs" + ); + } + + @Test + void motdSelector_withSchedule_excludesMotdOutsideCurrentUtcWindow(@TempDir final Path tempDir) throws IOException { + final Path file = copyFixture(tempDir, "motd-selector-time-and-priority.conf"); + final var motds = new MOTDSettings(loadMotdConfig(file)).getMotdRepository().motds(); + final MOTD selected = MOTDSelector.select(motds); + assertEquals("fallback-default-schedule", selected.line1()); + } + + @Test + void motdSelector_withSchedule_andWithoutSchedule_picksBestPriorityAmongActive(@TempDir final Path tempDir) throws IOException { + final Path file = copyFixture(tempDir, "motd-selector-time-wide-priority.conf"); + final var motds = new MOTDSettings(loadMotdConfig(file)).getMotdRepository().motds(); + final MOTD selected = MOTDSelector.select(motds); + assertEquals("always-active-best-priority", selected.line1()); + } + + private static MOTDConfig loadMotdConfig(final Path path) throws ConfigurateException { + return new ConfigLoader<>(MOTDConfig.class, path).load(); + } + + private static MOTD firstMotd(final MOTDConfig config) { + return new MOTDSettings(config).getMotdRepository().motds().get(0); + } + + private Path copyFixture(final Path tempDir, final String name) throws IOException { + final String resourcePath = "/integration/" + name; + final InputStream stream = MOTDTimeRangeConfigIntegrationTest.class.getResourceAsStream(resourcePath); + if (stream == null) { + throw new IllegalStateException("Missing classpath resource: " + resourcePath); + } + final Path dest = tempDir.resolve(name); + try (stream) { + Files.copy(stream, dest, StandardCopyOption.REPLACE_EXISTING); + } + return dest; + } +} diff --git a/common/src/test/resources/integration/motd-selector-priority-no-schedule.conf b/common/src/test/resources/integration/motd-selector-priority-no-schedule.conf new file mode 100644 index 00000000..66da4d4d --- /dev/null +++ b/common/src/test/resources/integration/motd-selector-priority-no-schedule.conf @@ -0,0 +1,18 @@ +motds=[ + { + line1="lower-priority-wins", + line2="", + schedule-settings { + priority=1, + weight=1 + } + }, + { + line1="ignored-higher-priority-number", + line2="", + schedule-settings { + priority=50, + weight=100 + } + } +] diff --git a/common/src/test/resources/integration/motd-selector-priority-weight-no-schedule.conf b/common/src/test/resources/integration/motd-selector-priority-weight-no-schedule.conf new file mode 100644 index 00000000..990d6a2c --- /dev/null +++ b/common/src/test/resources/integration/motd-selector-priority-weight-no-schedule.conf @@ -0,0 +1,18 @@ +motds=[ + { + line1="candidate-a", + line2="", + schedule-settings { + priority=1, + weight=1 + } + }, + { + line1="candidate-b", + line2="", + schedule-settings { + priority=1, + weight=1 + } + } +] diff --git a/common/src/test/resources/integration/motd-selector-time-and-priority.conf b/common/src/test/resources/integration/motd-selector-time-and-priority.conf new file mode 100644 index 00000000..b2a8a1f9 --- /dev/null +++ b/common/src/test/resources/integration/motd-selector-time-and-priority.conf @@ -0,0 +1,21 @@ +motds=[ + { + line1="best-priority-but-future-only", + line2="", + schedule-settings { + priority=1, + weight=1, + time-schedule=[ + { from="2099-01-01T00:00:00", to="2099-12-31T23:59:59" } + ] + } + }, + { + line1="fallback-default-schedule", + line2="", + schedule-settings { + priority=99, + weight=1 + } + } +] diff --git a/common/src/test/resources/integration/motd-selector-time-wide-priority.conf b/common/src/test/resources/integration/motd-selector-time-wide-priority.conf new file mode 100644 index 00000000..cefc827a --- /dev/null +++ b/common/src/test/resources/integration/motd-selector-time-wide-priority.conf @@ -0,0 +1,21 @@ +motds=[ + { + line1="scheduled-but-worse-priority", + line2="", + schedule-settings { + priority=20, + weight=1, + time-schedule=[ + { from="2000-01-01T00:00:00", to="2100-12-31T23:59:59" } + ] + } + }, + { + line1="always-active-best-priority", + line2="", + schedule-settings { + priority=1, + weight=1 + } + } +] diff --git a/common/src/test/resources/integration/motd-timerange-exact.conf b/common/src/test/resources/integration/motd-timerange-exact.conf new file mode 100644 index 00000000..cd829c79 --- /dev/null +++ b/common/src/test/resources/integration/motd-timerange-exact.conf @@ -0,0 +1,11 @@ +motds=[ + { + line1="Exact range MOTD", + line2="line2", + schedule-settings { + time-schedule=[ + { from="2024-06-15T10:00:00", to="2024-06-15T18:00:00" } + ] + } + } +] diff --git a/common/src/test/resources/integration/motd-timerange-invalid-inverted.conf b/common/src/test/resources/integration/motd-timerange-invalid-inverted.conf new file mode 100644 index 00000000..7b73c652 --- /dev/null +++ b/common/src/test/resources/integration/motd-timerange-invalid-inverted.conf @@ -0,0 +1,11 @@ +motds=[ + { + line1="bad", + line2="bad", + schedule-settings { + time-schedule=[ + { from="2024-06-15T18:00:00", to="2024-06-15T10:00:00" } + ] + } + } +] diff --git a/common/src/test/resources/integration/motd-timerange-invalid-mixed.conf b/common/src/test/resources/integration/motd-timerange-invalid-mixed.conf new file mode 100644 index 00000000..54886ed4 --- /dev/null +++ b/common/src/test/resources/integration/motd-timerange-invalid-mixed.conf @@ -0,0 +1,11 @@ +motds=[ + { + line1="bad", + line2="bad", + schedule-settings { + time-schedule=[ + { from="2024-06-15T10:00:00", to="17:00" } + ] + } + } +] diff --git a/common/src/test/resources/integration/motd-timerange-offset.conf b/common/src/test/resources/integration/motd-timerange-offset.conf new file mode 100644 index 00000000..6c94d25d --- /dev/null +++ b/common/src/test/resources/integration/motd-timerange-offset.conf @@ -0,0 +1,11 @@ +motds=[ + { + line1="Offset MOTD", + line2="line2", + schedule-settings { + time-schedule=[ + { from="2024-06-15T11:00:00+01:00", to="2024-06-15T19:00:00+01:00" } + ] + } + } +] diff --git a/common/src/test/resources/integration/motd-timerange-timeofday.conf b/common/src/test/resources/integration/motd-timerange-timeofday.conf new file mode 100644 index 00000000..9f36ac38 --- /dev/null +++ b/common/src/test/resources/integration/motd-timerange-timeofday.conf @@ -0,0 +1,11 @@ +motds=[ + { + line1="Time-of-day MOTD", + line2="line2", + schedule-settings { + time-schedule=[ + { from="09:00", to="17:00" } + ] + } + } +] From bbcefa6edd63ab12f095b295bb9ac395052dd97e Mon Sep 17 00:00:00 2001 From: av Date: Sun, 10 May 2026 01:20:17 +0300 Subject: [PATCH 7/8] fixed: config comment on time range --- .../java/xyz/jpenilla/minimotd/common/config/MOTDConfig.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDConfig.java b/common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDConfig.java index 633486fc..770475ad 100644 --- a/common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDConfig.java +++ b/common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDConfig.java @@ -76,8 +76,7 @@ public static final class MOTDScheduleSettings { private int priority = 1; @Comment("Time ranges when MOTD should be shown\n" + - " If none set - then could be shown at any time\n" + - " Time-zone is UTC") + " If none set - then could be shown at any time") private List timeSchedule = List.of(); public int getWeight() { From f989c897badaec31847bb5d66b551bcef95b792d Mon Sep 17 00:00:00 2001 From: av Date: Sun, 10 May 2026 09:28:23 +0300 Subject: [PATCH 8/8] improved: secured, that MOTDs always have a fallback --- .../common/config/MOTDRepository.java | 11 ++++++++-- .../MOTDTimeRangeConfigIntegrationTest.java | 8 ++++++++ .../integration/motd-timerange-exact.conf | 4 ++++ .../motd-timerange-no-fallback.conf | 20 +++++++++++++++++++ .../integration/motd-timerange-offset.conf | 4 ++++ .../integration/motd-timerange-timeofday.conf | 4 ++++ 6 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 common/src/test/resources/integration/motd-timerange-no-fallback.conf diff --git a/common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDRepository.java b/common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDRepository.java index 2f91bc58..508985ea 100644 --- a/common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDRepository.java +++ b/common/src/main/java/xyz/jpenilla/minimotd/common/config/MOTDRepository.java @@ -25,9 +25,16 @@ static MOTDRepository fromConfig(final MOTDConfig config) { return new MOTDRepository(List.of()); } - return new MOTDRepository(config.motds().stream() + final List motds = config.motds().stream() .map(MOTDAdapter::new) - .toList()); + .toList(); + + if (!motds.isEmpty() && motds.stream().noneMatch(motd -> motd.timeRanges().isEmpty())) { + // we have to request have at least one default MOTD until there is no algorithm which ensure that existing time ranges cover any time + throw new IllegalStateException("Required at least one MOTD without time range to have a fallback"); + } + + return new MOTDRepository(motds); } private static final class MOTDAdapter implements MOTD { diff --git a/common/src/test/java/xyz/jpenilla/minimotd/common/config/MOTDTimeRangeConfigIntegrationTest.java b/common/src/test/java/xyz/jpenilla/minimotd/common/config/MOTDTimeRangeConfigIntegrationTest.java index 66a504db..24ae2b83 100644 --- a/common/src/test/java/xyz/jpenilla/minimotd/common/config/MOTDTimeRangeConfigIntegrationTest.java +++ b/common/src/test/java/xyz/jpenilla/minimotd/common/config/MOTDTimeRangeConfigIntegrationTest.java @@ -74,6 +74,14 @@ void explicitOffsetParsedAsUtcInstant_fromHocon(@TempDir final Path tempDir) thr assertFalse(range.isInTimeRange(OffsetDateTime.of(2024, 6, 15, 19, 0, 0, 0, ZoneOffset.UTC))); } + @Test + void allMotdsScheduled_withoutFallback_throws(@TempDir final Path tempDir) throws IOException { + final Path file = copyFixture(tempDir, "motd-timerange-no-fallback.conf"); + final MOTDConfig config = loadMotdConfig(file); + final IllegalStateException ex = assertThrows(IllegalStateException.class, () -> new MOTDSettings(config)); + assertEquals("Required at least one MOTD without time range to have a fallback", ex.getMessage()); + } + @Test void mixedDateTimeAndTimeOfDay_throws(@TempDir final Path tempDir) throws IOException { final Path file = copyFixture(tempDir, "motd-timerange-invalid-mixed.conf"); diff --git a/common/src/test/resources/integration/motd-timerange-exact.conf b/common/src/test/resources/integration/motd-timerange-exact.conf index cd829c79..7d4d421d 100644 --- a/common/src/test/resources/integration/motd-timerange-exact.conf +++ b/common/src/test/resources/integration/motd-timerange-exact.conf @@ -7,5 +7,9 @@ motds=[ { from="2024-06-15T10:00:00", to="2024-06-15T18:00:00" } ] } + }, + { + line1="fallback" + line2="unused" } ] diff --git a/common/src/test/resources/integration/motd-timerange-no-fallback.conf b/common/src/test/resources/integration/motd-timerange-no-fallback.conf new file mode 100644 index 00000000..0e9da98e --- /dev/null +++ b/common/src/test/resources/integration/motd-timerange-no-fallback.conf @@ -0,0 +1,20 @@ +motds=[ + { + line1="Morning slot" + line2="a" + schedule-settings { + time-schedule=[ + { from="09:00", to="12:00" } + ] + } + }, + { + line1="Afternoon slot" + line2="b" + schedule-settings { + time-schedule=[ + { from="13:00", to="17:00" } + ] + } + } +] diff --git a/common/src/test/resources/integration/motd-timerange-offset.conf b/common/src/test/resources/integration/motd-timerange-offset.conf index 6c94d25d..4c7a5b6b 100644 --- a/common/src/test/resources/integration/motd-timerange-offset.conf +++ b/common/src/test/resources/integration/motd-timerange-offset.conf @@ -7,5 +7,9 @@ motds=[ { from="2024-06-15T11:00:00+01:00", to="2024-06-15T19:00:00+01:00" } ] } + }, + { + line1="fallback" + line2="unused" } ] diff --git a/common/src/test/resources/integration/motd-timerange-timeofday.conf b/common/src/test/resources/integration/motd-timerange-timeofday.conf index 9f36ac38..f4f0183c 100644 --- a/common/src/test/resources/integration/motd-timerange-timeofday.conf +++ b/common/src/test/resources/integration/motd-timerange-timeofday.conf @@ -7,5 +7,9 @@ motds=[ { from="09:00", to="17:00" } ] } + }, + { + line1="fallback" + line2="unused" } ]