e : ordered) {
+ var key = e.getKey();
+ var replacement = e.getValue();
+
+ var config = TextReplacementConfig.builder()
+ .matchLiteral(key)
+ .replacement(replacement)
+ .build();
+
+ out = out.replaceText(config);
+ }
+
+ return out;
+ }
+}
diff --git a/automessage-core/src/main/java/com/github/imdmk/automessage/shared/adventure/AdventurePlaceholders.java b/automessage-core/src/main/java/com/github/imdmk/automessage/shared/adventure/AdventurePlaceholders.java
new file mode 100644
index 0000000..70a085a
--- /dev/null
+++ b/automessage-core/src/main/java/com/github/imdmk/automessage/shared/adventure/AdventurePlaceholders.java
@@ -0,0 +1,158 @@
+package com.github.imdmk.automessage.shared.adventure;
+
+import com.github.imdmk.automessage.shared.validate.Validator;
+import net.kyori.adventure.text.Component;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Unmodifiable;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Immutable container mapping literal placeholder keys to Adventure {@link Component} values.
+ *
+ * Instances are created via the {@link Builder}. Once built, the mapping is read-only.
+ *
+ * Thread-safety: Fully immutable and safe for concurrent use.
+ */
+public final class AdventurePlaceholders {
+
+ private static final AdventurePlaceholders EMPTY = new AdventurePlaceholders(Map.of());
+
+ private final Map map;
+
+ private AdventurePlaceholders(@NotNull Map map) {
+ Validator.notNull(map, "map cannot be null");
+ this.map = Collections.unmodifiableMap(map);
+ }
+
+ /**
+ * Returns an unmodifiable view of all placeholder mappings.
+ *
+ * @return unmodifiable placeholder map
+ */
+ @Unmodifiable
+ @NotNull
+ public Map asMap() {
+ return map;
+ }
+
+ /**
+ * Returns the number of registered placeholders.
+ *
+ * @return placeholder count
+ */
+ public int size() {
+ return map.size();
+ }
+
+ /**
+ * Checks if the placeholder map is empty.
+ *
+ * @return {@code true} if no placeholders are defined
+ */
+ public boolean isEmpty() {
+ return map.isEmpty();
+ }
+
+ /**
+ * Returns a shared immutable empty instance.
+ *
+ * @return empty placeholder container
+ */
+ public static @NotNull AdventurePlaceholders empty() {
+ return EMPTY;
+ }
+
+ /**
+ * Creates a new builder for {@link AdventurePlaceholders}.
+ *
+ * @return new builder instance
+ */
+ public static @NotNull Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Fluent builder for {@link AdventurePlaceholders}.
+ */
+ public static final class Builder {
+
+ private final Map entries = new LinkedHashMap<>();
+
+ /**
+ * Adds a literal → component mapping.
+ *
+ * @param key literal placeholder key
+ * @param value replacement component
+ * @return this builder for chaining
+ */
+ @Contract("_,_ -> this")
+ public @NotNull Builder with(@NotNull String key, @NotNull Component value) {
+ Validator.notNull(key, "key cannot be null");
+ Validator.notNull(value, "value cannot be null");
+ this.entries.put(key, value);
+ return this;
+ }
+
+ /**
+ * Adds a literal → plain text mapping (converted to {@link Component#text(String)}).
+ *
+ * @param key literal placeholder key
+ * @param value replacement text
+ * @return this builder for chaining
+ */
+ @Contract("_,_ -> this")
+ public @NotNull Builder with(@NotNull String key, @NotNull String value) {
+ Validator.notNull(key, "key cannot be null");
+ Validator.notNull(value, "value cannot be null");
+ this.entries.put(key, Component.text(value));
+ return this;
+ }
+
+ /**
+ * Adds all entries from another {@link AdventurePlaceholders}.
+ *
+ * @param other another placeholder container
+ * @return this builder for chaining
+ */
+ @Contract("_ -> this")
+ public @NotNull Builder with(@NotNull AdventurePlaceholders other) {
+ Validator.notNull(other, "other cannot be null");
+ this.entries.putAll(other.asMap());
+ return this;
+ }
+
+ /**
+ * Adds a placeholder using any object value.
+ * The value is converted to plain text via {@link String#valueOf(Object)}.
+ *
+ * @param key placeholder key
+ * @param value object to convert and insert
+ * @return this builder for chaining
+ * @throws NullPointerException if key or value is null
+ */
+ @Contract("_,_ -> this")
+ public @NotNull Builder with(@NotNull String key, @NotNull Object value) {
+ Validator.notNull(key, "key cannot be null");
+ Validator.notNull(value, "value cannot be null");
+ this.entries.put(key, Component.text(String.valueOf(value)));
+ return this;
+ }
+
+ /**
+ * Builds an immutable {@link AdventurePlaceholders} instance.
+ *
+ * @return immutable placeholder container
+ */
+ public @NotNull AdventurePlaceholders build() {
+ if (this.entries.isEmpty()) {
+ return EMPTY;
+ }
+
+ return new AdventurePlaceholders(new LinkedHashMap<>(this.entries));
+ }
+ }
+}
diff --git a/automessage-core/src/main/java/com/github/imdmk/automessage/shared/message/MessageConfig.java b/automessage-core/src/main/java/com/github/imdmk/automessage/shared/message/MessageConfig.java
new file mode 100644
index 0000000..fd7d5f8
--- /dev/null
+++ b/automessage-core/src/main/java/com/github/imdmk/automessage/shared/message/MessageConfig.java
@@ -0,0 +1,77 @@
+package com.github.imdmk.automessage.shared.message;
+
+import com.eternalcode.multification.notice.resolver.NoticeResolverDefaults;
+import com.eternalcode.multification.okaeri.MultificationSerdesPack;
+import com.github.imdmk.automessage.command.dispatcher.messages.DispatcherMessagesImpl;
+import com.github.imdmk.automessage.command.reload.messages.ReloadMessagesImpl;
+import com.github.imdmk.automessage.config.ConfigSection;
+import com.github.imdmk.automessage.platform.litecommands.messages.LiteCommandsMessagesImpl;
+import eu.okaeri.configs.annotation.Comment;
+import eu.okaeri.configs.annotation.Header;
+import eu.okaeri.configs.serdes.OkaeriSerdesPack;
+import org.jetbrains.annotations.NotNull;
+
+@Header({
+ "# ============================================================================",
+ "# AutoMessage — messages.yml",
+ "# ============================================================================",
+ "# This configuration file defines all user-facing messages used by AutoMessage.",
+ "# It centralizes command responses, dispatcher status messages, and reload",
+ "# notifications, allowing full customization without modifying code.",
+ "#",
+ "# Sections:",
+ "# • liteCommandsMessages — errors, usage prompts, and permission responses",
+ "# • dispatcherMessages — messages for enabling/disabling automatic dispatching",
+ "# • reloadMessages — messages shown during configuration reload operations",
+ "#",
+ "# Editing Tips:",
+ "# • Colors follow MiniMessage syntax (, , , , etc.).",
+ "# • Reload the plugin after editing: /automessage reload",
+ "#",
+ "# Source Code:",
+ "# https://github.com/imDMK/AutoMessage",
+ "#",
+ "# Support development:",
+ "# GitHub Sponsors: https://github.com/sponsors/imDMK",
+ "# PayPal: https://paypal.me/dominiksuliga",
+ "#",
+ "# Thank you for using AutoMessage!",
+ "# ============================================================================"
+})
+public final class MessageConfig extends ConfigSection {
+
+ @Comment({
+ "#",
+ "# Messages used by the LiteCommands subsystem.",
+ "# Contains permission errors, usage hints, and syntax messages.",
+ "#"
+ })
+ public LiteCommandsMessagesImpl liteCommandsMessages = new LiteCommandsMessagesImpl();
+
+ @Comment({
+ "#",
+ "# Messages used by dispatcher-related commands.",
+ "#"
+ })
+ public DispatcherMessagesImpl dispatcherMessages = new DispatcherMessagesImpl();
+
+ @Comment({
+ "#",
+ "# Messages used during configuration reload commands.",
+ "# Includes success and failure notifications.",
+ "#"
+ })
+ public ReloadMessagesImpl reloadMessages = new ReloadMessagesImpl();
+
+ @Override
+ public @NotNull OkaeriSerdesPack getSerdesPack() {
+ return registry -> {
+ registry.register(new MultificationSerdesPack(NoticeResolverDefaults.createRegistry()));
+ };
+ }
+
+ @Override
+ public @NotNull String getFileName() {
+ return "messages.yml";
+ }
+}
diff --git a/automessage-core/src/main/java/com/github/imdmk/automessage/shared/message/MessageService.java b/automessage-core/src/main/java/com/github/imdmk/automessage/shared/message/MessageService.java
new file mode 100644
index 0000000..c2783cc
--- /dev/null
+++ b/automessage-core/src/main/java/com/github/imdmk/automessage/shared/message/MessageService.java
@@ -0,0 +1,86 @@
+package com.github.imdmk.automessage.shared.message;
+
+import com.eternalcode.multification.adventure.AudienceConverter;
+import com.eternalcode.multification.bukkit.BukkitMultification;
+import com.eternalcode.multification.notice.provider.NoticeProvider;
+import com.eternalcode.multification.translation.TranslationProvider;
+import com.github.imdmk.automessage.shared.validate.Validator;
+import net.kyori.adventure.platform.AudienceProvider;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.kyori.adventure.text.serializer.ComponentSerializer;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+public final class MessageService extends BukkitMultification {
+
+ private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
+
+ private final MessageConfig messageConfig;
+ private final AudienceProvider audienceProvider;
+
+ public MessageService(
+ @NotNull MessageConfig messageConfig,
+ @NotNull AudienceProvider audienceProvider
+ ) {
+ this.messageConfig = Validator.notNull(messageConfig, "messageConfig cannot be null");
+ this.audienceProvider = Validator.notNull(audienceProvider, "audienceProvider cannot be null");
+ }
+
+ /**
+ * Returns a translation provider that always returns the same {@link MessageConfig} instance,
+ * ignoring locale differences.
+ *
+ * @return locale-agnostic translation provider
+ */
+ @Override
+ protected @NotNull TranslationProvider translationProvider() {
+ return provider -> messageConfig;
+ }
+
+ /**
+ * Returns the {@link MiniMessage}-based component serializer.
+ *
+ * @return component serializer for text serialization/deserialization
+ */
+ @Override
+ protected @NotNull ComponentSerializer serializer() {
+ return MINI_MESSAGE;
+ }
+
+ /**
+ * Converts Bukkit {@link CommandSender}s into Adventure audiences
+ * using the configured {@link AudienceProvider}.
+ *
+ * Players are mapped to player audiences, while other senders
+ * (e.g., console or command blocks) are mapped to {@link AudienceProvider#console()}.
+ *
+ * @return non-null audience converter
+ */
+ @Override
+ protected @NotNull AudienceConverter audienceConverter() {
+ return sender -> {
+ if (sender instanceof Player player) {
+ return audienceProvider.player(player.getUniqueId());
+ }
+ return audienceProvider.console();
+ };
+ }
+
+ /**
+ * Sends a localized or static notice message to the specified Bukkit {@link CommandSender}.
+ *
+ * The notice is resolved through the active {@link MessageConfig}
+ * and rendered using {@link MiniMessage} formatting.
+ *
+ * @param sender non-null Bukkit command sender (player, console, etc.)
+ * @param notice non-null notice provider bound to {@link MessageConfig}
+ * @throws NullPointerException if {@code sender} or {@code notice} is null
+ */
+ public void send(@NotNull CommandSender sender, @NotNull NoticeProvider notice) {
+ Validator.notNull(sender, "sender cannot be null");
+ Validator.notNull(notice, "notice cannot be null");
+ create().viewer(sender).notice(notice).send();
+ }
+}
diff --git a/automessage-core/src/main/java/com/github/imdmk/automessage/shared/validate/Validator.java b/automessage-core/src/main/java/com/github/imdmk/automessage/shared/validate/Validator.java
new file mode 100644
index 0000000..2e20606
--- /dev/null
+++ b/automessage-core/src/main/java/com/github/imdmk/automessage/shared/validate/Validator.java
@@ -0,0 +1,65 @@
+package com.github.imdmk.automessage.shared.validate;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.function.Consumer;
+
+/**
+ * Utility class for common validation checks.
+ *
+ * Provides null-safety guards used throughout the codebase.
+ */
+public final class Validator {
+
+ private Validator() {
+ throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
+ }
+
+ /**
+ * Ensures the given object is not {@code null}.
+ *
+ * This method is typically used to validate constructor arguments and
+ * configuration values. If the supplied object is non-null, it is returned
+ * unchanged; otherwise a {@link NullPointerException} is thrown with the
+ * provided message.
+ *
+ * @param obj the value to validate; may be null
+ * @param context exception context used when {@code obj} is null
+ * @param type of the validated object
+ * @return the non-null value of {@code obj}
+ * @throws NullPointerException if {@code obj} is null
+ */
+ public static T notNull(@Nullable T obj, @NotNull String context) {
+ if (obj == null) {
+ throw new NullPointerException(context + " cannot be null");
+ }
+ return obj;
+ }
+
+ /**
+ * Executes the given {@link Consumer} only if the supplied object is not {@code null}.
+ *
+ * This helper is especially useful during shutdown or cleanup phases where
+ * optional components may or may not be initialized. The consumer itself
+ * must be non-null; however, it will only be invoked when {@code obj} is non-null.
+ *
+ *
Example usage:
+ *
+ * Validator.ifNotNull(taskScheduler, TaskScheduler::shutdown);
+ * Validator.ifNotNull(messageService, MessageService::shutdown);
+ *
+ *
+ * @param obj the object to check before executing the consumer; may be null
+ * @param consumer operation to execute when {@code obj} is non-null (never null)
+ * @param type of the object passed to the consumer
+ * @throws NullPointerException if {@code consumer} is null
+ */
+ public static void ifNotNull(@Nullable T obj, @NotNull Consumer consumer) {
+ Validator.notNull(consumer, "consumer is null");
+ if (obj != null) {
+ consumer.accept(obj);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/automessage-core/src/test/java/com/github/imdmk/automessage/config/ConfigManagerTest.java b/automessage-core/src/test/java/com/github/imdmk/automessage/config/ConfigManagerTest.java
new file mode 100644
index 0000000..9eb08e7
--- /dev/null
+++ b/automessage-core/src/test/java/com/github/imdmk/automessage/config/ConfigManagerTest.java
@@ -0,0 +1,128 @@
+package com.github.imdmk.automessage.config;
+
+import com.github.imdmk.automessage.platform.logger.PluginLogger;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertNull;
+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.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class ConfigManagerTest {
+
+ private ConfigManager manager;
+
+ private ConfigFactory factory;
+ private ConfigBinder binder;
+ private ConfigLifecycle lifecycle;
+
+ private final PluginLogger logger = mock(PluginLogger.class);
+ private final File dataFolder = new File("build/test-configs");
+
+ @BeforeEach
+ @DisplayName("Setup test environment and inject mock dependencies")
+ void setUp() {
+ manager = new ConfigManager(logger, dataFolder);
+
+ factory = mock(ConfigFactory.class);
+ binder = mock(ConfigBinder.class);
+ lifecycle = mock(ConfigLifecycle.class);
+
+ inject("factory", factory);
+ inject("binder", binder);
+ inject("lifecycle", lifecycle);
+ }
+
+ @Test
+ @DisplayName("create(): should instantiate, bind, initialize and register config")
+ void create_shouldInstantiateBindInitializeAndRegister() {
+ // given
+ SampleConfig config = new SampleConfig();
+ when(factory.instantiate(SampleConfig.class)).thenReturn(config);
+
+ // when
+ SampleConfig result = manager.create(SampleConfig.class);
+
+ // then
+ assertSame(config, result);
+
+ verify(factory).instantiate(SampleConfig.class);
+ verify(binder).bind(eq(config), any(File.class));
+ verify(lifecycle).initialize(config);
+
+ assertTrue(manager.getConfigs().contains(config));
+ assertSame(config, manager.get(SampleConfig.class));
+ }
+
+ @Test
+ @DisplayName("createAll(): should call create() for each provided config class")
+ void createAll_shouldCreateEachConfig() {
+ when(factory.instantiate(SampleConfig.class)).thenReturn(new SampleConfig());
+
+ manager.createAll(List.of(SampleConfig.class));
+
+ verify(factory).instantiate(SampleConfig.class);
+ }
+
+ @Test
+ @DisplayName("require(): should throw when config has not been created earlier")
+ void require_shouldThrowWhenNotCreated() {
+ assertThrows(IllegalStateException.class, () -> manager.require(SampleConfig.class));
+ }
+
+ @Test
+ @DisplayName("loadAll(): should delegate loading to lifecycle")
+ void loadAll_shouldDelegateToLifecycle() {
+ SampleConfig config = new SampleConfig();
+ when(factory.instantiate(SampleConfig.class)).thenReturn(config);
+
+ manager.create(SampleConfig.class);
+ manager.loadAll();
+
+ verify(lifecycle).load(config);
+ }
+
+ @Test
+ @DisplayName("saveAll(): should delegate saving to lifecycle")
+ void saveAll_shouldDelegateToLifecycle() {
+ SampleConfig config = new SampleConfig();
+ when(factory.instantiate(SampleConfig.class)).thenReturn(config);
+
+ manager.create(SampleConfig.class);
+ manager.saveAll();
+
+ verify(lifecycle).save(config);
+ }
+
+ @Test
+ @DisplayName("clearAll(): should remove all configs from registry")
+ void clearAll_shouldRemoveAllConfigs() {
+ when(factory.instantiate(SampleConfig.class)).thenReturn(new SampleConfig());
+
+ manager.create(SampleConfig.class);
+ manager.clearAll();
+
+ assertTrue(manager.getConfigs().isEmpty());
+ assertNull(manager.get(SampleConfig.class));
+ }
+
+ private void inject(String field, Object value) {
+ try {
+ var f = ConfigManager.class.getDeclaredField(field);
+ f.setAccessible(true);
+ f.set(manager, value);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/automessage-core/src/test/java/com/github/imdmk/automessage/config/SampleConfig.java b/automessage-core/src/test/java/com/github/imdmk/automessage/config/SampleConfig.java
new file mode 100644
index 0000000..e0c9562
--- /dev/null
+++ b/automessage-core/src/test/java/com/github/imdmk/automessage/config/SampleConfig.java
@@ -0,0 +1,20 @@
+package com.github.imdmk.automessage.config;
+
+import eu.okaeri.configs.serdes.OkaeriSerdesPack;
+import org.jetbrains.annotations.NotNull;
+
+public class SampleConfig extends ConfigSection {
+
+ public int value = 5;
+
+ @Override
+ public @NotNull OkaeriSerdesPack getSerdesPack() {
+ return registry -> {};
+ }
+
+ @Override
+ public @NotNull String getFileName() {
+ return "sample.yml";
+ }
+}
+
diff --git a/automessage-core/src/test/java/com/github/imdmk/automessage/scheduled/ScheduledMessageTest.java b/automessage-core/src/test/java/com/github/imdmk/automessage/scheduled/ScheduledMessageTest.java
new file mode 100644
index 0000000..aaeca09
--- /dev/null
+++ b/automessage-core/src/test/java/com/github/imdmk/automessage/scheduled/ScheduledMessageTest.java
@@ -0,0 +1,107 @@
+package com.github.imdmk.automessage.scheduled;
+
+import com.eternalcode.multification.notice.Notice;
+import com.github.imdmk.automessage.scheduled.audience.rule.AudienceRule;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class ScheduledMessageTest {
+
+ @Test
+ @DisplayName("Should create ScheduledMessage when valid arguments are provided")
+ void shouldCreateScheduledMessage() {
+ // given
+ var notice = Notice.chat("hello");
+ var rule = AudienceRule.permission("test.permission");
+
+ // when
+ ScheduledMessage message = new ScheduledMessage(
+ "example",
+ List.of(notice),
+ List.of(rule)
+ );
+
+ // then
+ assertEquals("example", message.name());
+ assertEquals(List.of(notice), message.notices());
+ assertEquals(List.of(rule), message.rules());
+ }
+
+ @Test
+ @DisplayName("Should throw when name is null")
+ void shouldFailWhenNameNull() {
+ var notice = Notice.chat("test");
+
+ assertThrows(NullPointerException.class, () ->
+ new ScheduledMessage(
+ null,
+ List.of(notice),
+ List.of()
+ )
+ );
+ }
+
+ @Test
+ @DisplayName("Should throw when notices list is null")
+ void shouldFailWhenNoticesNull() {
+ assertThrows(NullPointerException.class, () ->
+ new ScheduledMessage(
+ "msg",
+ null,
+ List.of()
+ )
+ );
+ }
+
+ @Test
+ @DisplayName("Should throw when notices list is empty")
+ void shouldFailWhenNoticesEmpty() {
+ assertThrows(IllegalArgumentException.class, () ->
+ new ScheduledMessage(
+ "msg",
+ List.of(),
+ List.of()
+ )
+ );
+ }
+
+ @Test
+ @DisplayName("Should produce unmodifiable notice and rule lists")
+ void shouldMakeCollectionsUnmodifiable() {
+ var notice = Notice.chat("test");
+ var rule = AudienceRule.permission("perm");
+
+ ScheduledMessage message = new ScheduledMessage(
+ "msg",
+ List.of(notice),
+ List.of(rule)
+ );
+
+ assertThrows(UnsupportedOperationException.class, () ->
+ message.notices().add(Notice.chat("other"))
+ );
+
+ assertThrows(UnsupportedOperationException.class, () ->
+ message.rules().add(AudienceRule.group("vip"))
+ );
+ }
+
+ @Test
+ @DisplayName("Record equality should compare field values")
+ void shouldRespectRecordEquality() {
+ var n1 = Notice.chat("x");
+ var r1 = AudienceRule.permission("p");
+
+ ScheduledMessage a = new ScheduledMessage("name", List.of(n1), List.of(r1));
+ ScheduledMessage b = new ScheduledMessage("name", List.of(n1), List.of(r1));
+
+ assertEquals(a, b);
+ assertEquals(a.hashCode(), b.hashCode());
+ }
+}
+
diff --git a/automessage-core/src/test/java/com/github/imdmk/automessage/scheduled/selector/MessageSelectorFactoryTest.java b/automessage-core/src/test/java/com/github/imdmk/automessage/scheduled/selector/MessageSelectorFactoryTest.java
new file mode 100644
index 0000000..f80107a
--- /dev/null
+++ b/automessage-core/src/test/java/com/github/imdmk/automessage/scheduled/selector/MessageSelectorFactoryTest.java
@@ -0,0 +1,38 @@
+package com.github.imdmk.automessage.scheduled.selector;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class MessageSelectorFactoryTest {
+
+ @Test
+ @DisplayName("Factory should create a RandomMessageSelector when RANDOM is chosen")
+ void shouldCreateRandomSelector() {
+ MessageSelector selector = MessageSelectorFactory.create(MessageSelectorType.RANDOM);
+
+ assertNotNull(selector);
+ assertEquals(RandomMessageSelector.class, selector.getClass());
+ }
+
+ @Test
+ @DisplayName("Factory should create a SequentialMessageSelector when SEQUENTIAL is chosen")
+ void shouldCreateSequentialSelector() {
+ MessageSelector selector = MessageSelectorFactory.create(MessageSelectorType.SEQUENTIAL);
+
+ assertNotNull(selector);
+ assertEquals(SequentialMessageSelector.class, selector.getClass());
+ }
+
+ @Test
+ @DisplayName("Factory should reject null type")
+ void shouldRejectNullType() {
+ assertThrows(NullPointerException.class, () ->
+ MessageSelectorFactory.create(null)
+ );
+ }
+}
+
diff --git a/automessage-core/src/test/java/com/github/imdmk/automessage/scheduled/selector/RandomMessageSelectorTest.java b/automessage-core/src/test/java/com/github/imdmk/automessage/scheduled/selector/RandomMessageSelectorTest.java
new file mode 100644
index 0000000..be0ebda
--- /dev/null
+++ b/automessage-core/src/test/java/com/github/imdmk/automessage/scheduled/selector/RandomMessageSelectorTest.java
@@ -0,0 +1,56 @@
+package com.github.imdmk.automessage.scheduled.selector;
+
+import com.eternalcode.multification.notice.Notice;
+import com.github.imdmk.automessage.scheduled.ScheduledMessage;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class RandomMessageSelectorTest {
+
+ private static ScheduledMessage msg(String name) {
+ return new ScheduledMessage(name, List.of(Notice.chat("x")), List.of());
+ }
+
+ @Test
+ @DisplayName("Random selector should return empty if list is empty")
+ void shouldReturnEmptyOnEmptyList() {
+ RandomMessageSelector selector = new RandomMessageSelector();
+
+ assertTrue(selector.selectNext(List.of()).isEmpty());
+ }
+
+ @Test
+ @DisplayName("Random selector should always return an element from the list")
+ void shouldReturnElementFromList() {
+ RandomMessageSelector selector = new RandomMessageSelector();
+ List messages = List.of(msg("a"), msg("b"), msg("c"));
+
+ ScheduledMessage result = selector.selectNext(messages).orElseThrow();
+
+ assertTrue(messages.contains(result));
+ }
+
+ @Test
+ @DisplayName("Random selector should return varying results across multiple calls")
+ void shouldReturnDifferentElements() {
+ RandomMessageSelector selector = new RandomMessageSelector();
+ List messages = List.of(msg("a"), msg("b"), msg("c"));
+
+ boolean variationFound = false;
+
+ ScheduledMessage first = selector.selectNext(messages).orElseThrow();
+
+ for (int i = 0; i < 50; i++) {
+ if (!selector.selectNext(messages).orElseThrow().equals(first)) {
+ variationFound = true;
+ break;
+ }
+ }
+
+ assertTrue(variationFound, "Random selector returned the same element in all iterations — suspicious randomness");
+ }
+}
diff --git a/automessage-core/src/test/java/com/github/imdmk/automessage/scheduled/selector/SequentialMessageSelectorTest.java b/automessage-core/src/test/java/com/github/imdmk/automessage/scheduled/selector/SequentialMessageSelectorTest.java
new file mode 100644
index 0000000..3c85203
--- /dev/null
+++ b/automessage-core/src/test/java/com/github/imdmk/automessage/scheduled/selector/SequentialMessageSelectorTest.java
@@ -0,0 +1,78 @@
+package com.github.imdmk.automessage.scheduled.selector;
+
+import com.eternalcode.multification.notice.Notice;
+import com.github.imdmk.automessage.scheduled.ScheduledMessage;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class SequentialMessageSelectorTest {
+
+ private static ScheduledMessage msg(String name) {
+ return new ScheduledMessage(name, List.of(Notice.chat("x")), List.of());
+ }
+
+ @Test
+ @DisplayName("Sequential selector should return empty if list is empty")
+ void shouldReturnEmptyOnEmptyList() {
+ SequentialMessageSelector selector = new SequentialMessageSelector();
+
+ assertTrue(selector.selectNext(List.of(), true).isEmpty());
+ }
+
+ @Test
+ @DisplayName("Sequential selector should iterate in order")
+ void shouldIterateSequentially() {
+ SequentialMessageSelector selector = new SequentialMessageSelector();
+ List messages = List.of(msg("a"), msg("b"), msg("c"));
+
+ assertEquals("a", selector.selectNext(messages).get().name());
+ assertEquals("b", selector.selectNext(messages).get().name());
+ assertEquals("c", selector.selectNext(messages).get().name());
+ assertEquals("a", selector.selectNext(messages).get().name()); // wraps around
+ }
+
+ @Test
+ @DisplayName("Sequential selector should not advance index when advanceIndex=false")
+ void shouldNotAdvanceIndexWhenFlagFalse() {
+ SequentialMessageSelector selector = new SequentialMessageSelector();
+ List messages = List.of(msg("a"), msg("b"));
+
+ assertEquals("a", selector.selectNext(messages, false).get().name());
+ assertEquals("a", selector.selectNext(messages, false).get().name());
+ assertEquals("a", selector.selectNext(messages, false).get().name());
+ }
+
+ @Test
+ @DisplayName("Sequential selector should advance index when advanceIndex=true")
+ void shouldAdvanceIndexWhenTrue() {
+ SequentialMessageSelector selector = new SequentialMessageSelector();
+ List messages = List.of(msg("a"), msg("b"));
+
+ assertEquals("a", selector.selectNext(messages, true).get().name());
+ assertEquals("b", selector.selectNext(messages, true).get().name());
+ assertEquals("a", selector.selectNext(messages, true).get().name()); // wraps
+ }
+
+ @Test
+ @DisplayName("Sequential selector should reset index after reaching threshold")
+ void shouldResetAfterThreshold() {
+ SequentialMessageSelector selector = new SequentialMessageSelector();
+ List messages = List.of(msg("a"));
+
+ // simulate state: index = threshold - 1
+ for (int i = 0; i < 1_000_000_000 - 1; i++) {
+ selector.selectNext(messages, true);
+ }
+
+ // next call should reset to 0, still producing valid output
+ ScheduledMessage result = selector.selectNext(messages, true).orElseThrow();
+
+ assertEquals("a", result.name());
+ }
+}
+
diff --git a/automessage-core/src/test/java/com/github/imdmk/automessage/shared/adventure/AdventureComponentsTest.java b/automessage-core/src/test/java/com/github/imdmk/automessage/shared/adventure/AdventureComponentsTest.java
new file mode 100644
index 0000000..7523efd
--- /dev/null
+++ b/automessage-core/src/test/java/com/github/imdmk/automessage/shared/adventure/AdventureComponentsTest.java
@@ -0,0 +1,61 @@
+package com.github.imdmk.automessage.shared.adventure;
+
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.TextDecoration;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class AdventureComponentsTest {
+
+ @Test
+ void text_single_shouldDeserialize() {
+ Component c = AdventureComponents.text("Hello");
+
+ assertThat(c)
+ .extracting(comp -> comp.decoration(TextDecoration.BOLD))
+ .isNotNull();
+ }
+
+ @Test
+ void text_varargs_shouldDeserializeList() {
+ List list = AdventureComponents.text("A", "B");
+
+ assertThat(list).hasSize(2);
+ }
+
+ @Test
+ void text_iterable_shouldDeserializeAll() {
+ List list = AdventureComponents.text(List.of("X", "Y"));
+
+ assertThat(list).hasSize(2);
+ }
+
+ @Test
+ void withoutItalics_component_shouldDisableItalic() {
+ Component c = AdventureComponents.text("Hello");
+ Component result = AdventureComponents.withoutItalics(c);
+
+ assertThat(result.decoration(TextDecoration.ITALIC)).isEqualTo(TextDecoration.State.FALSE);
+ }
+
+ @Test
+ void withoutItalics_stringVarargs_shouldDisableItalic() {
+ List out = AdventureComponents.withoutItalics("A", "B");
+
+ assertThat(out).hasSize(2);
+ out.forEach(c ->
+ assertThat(c.decoration(TextDecoration.ITALIC)).isEqualTo(TextDecoration.State.FALSE));
+ }
+
+ @Test
+ void serialize_shouldReturnMiniMessage() {
+ Component c = Component.text("Test");
+ String s = AdventureComponents.serialize(c);
+
+ assertThat(s).isEqualTo("Test");
+ }
+}
+
diff --git a/automessage-core/src/test/java/com/github/imdmk/automessage/shared/adventure/AdventureFormatterTest.java b/automessage-core/src/test/java/com/github/imdmk/automessage/shared/adventure/AdventureFormatterTest.java
new file mode 100644
index 0000000..d64e0bf
--- /dev/null
+++ b/automessage-core/src/test/java/com/github/imdmk/automessage/shared/adventure/AdventureFormatterTest.java
@@ -0,0 +1,73 @@
+package com.github.imdmk.automessage.shared.adventure;
+
+import net.kyori.adventure.text.Component;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class AdventureFormatterTest {
+
+ @Test
+ void format_string_shouldApplyPlaceholder() {
+ AdventurePlaceholders ph = AdventurePlaceholders.builder()
+ .with("%name%", "DMK")
+ .build();
+
+ Component result = AdventureFormatter.format("Hello %name%", ph);
+
+ assertThat(result.toString()).contains("DMK");
+ }
+
+ @Test
+ void format_component_shouldApplyPlaceholder() {
+ Component base = Component.text("XP: %xp%");
+ AdventurePlaceholders ph = AdventurePlaceholders.builder()
+ .with("%xp%", "1500")
+ .build();
+
+ Component out = AdventureFormatter.format(base, ph);
+
+ assertThat(out.toString()).contains("1500");
+ }
+
+ @Test
+ void format_list_shouldApplyPlaceholdersToAll() {
+ AdventurePlaceholders ph = AdventurePlaceholders.builder()
+ .with("%v%", "VALUE")
+ .build();
+
+ List out = AdventureFormatter.format(
+ List.of(Component.text("A %v%"), Component.text("B %v%")),
+ ph
+ );
+
+ assertThat(out).hasSize(2);
+ assertThat(out.get(0).toString()).contains("VALUE");
+ assertThat(out.get(1).toString()).contains("VALUE");
+ }
+
+ @Test
+ void format_shouldHandleOverlappingKeysByLength() {
+ AdventurePlaceholders ph = AdventurePlaceholders.builder()
+ .with("%player%", "DOM")
+ .with("%player_name%", "DMK")
+ .build();
+
+ Component base = Component.text("Hello %player_name% !");
+ Component out = AdventureFormatter.format(base, ph);
+
+ assertThat(out.toString()).contains("DMK");
+ assertThat(out.toString()).doesNotContain("DOM");
+ }
+
+ @Test
+ void format_emptyPlaceholders_shouldReturnSameComponent() {
+ Component c = Component.text("Test");
+ Component out = AdventureFormatter.format(c, AdventurePlaceholders.empty());
+
+ assertThat(out).isSameAs(c);
+ }
+}
+
diff --git a/automessage-core/src/test/java/com/github/imdmk/automessage/shared/adventure/AdventurePlaceholdersTest.java b/automessage-core/src/test/java/com/github/imdmk/automessage/shared/adventure/AdventurePlaceholdersTest.java
new file mode 100644
index 0000000..e61d49f
--- /dev/null
+++ b/automessage-core/src/test/java/com/github/imdmk/automessage/shared/adventure/AdventurePlaceholdersTest.java
@@ -0,0 +1,81 @@
+package com.github.imdmk.automessage.shared.adventure;
+
+import net.kyori.adventure.text.Component;
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class AdventurePlaceholdersTest {
+
+ @Test
+ void empty_shouldReturnSingleton() {
+ AdventurePlaceholders p1 = AdventurePlaceholders.empty();
+ AdventurePlaceholders p2 = AdventurePlaceholders.empty();
+
+ assertThat(p1).isSameAs(p2);
+ assertThat(p1.size()).isZero();
+ }
+
+ @Test
+ void builder_withStringAndComponent_shouldStoreMapping() {
+ Component value = Component.text("Hello");
+ AdventurePlaceholders ph = AdventurePlaceholders.builder()
+ .with("%player%", value)
+ .build();
+
+ assertThat(ph.asMap())
+ .containsEntry("%player%", value);
+ }
+
+ @Test
+ void builder_withStringStringShouldConvertToTextComponent() {
+ AdventurePlaceholders ph = AdventurePlaceholders.builder()
+ .with("%x%", "Hello")
+ .build();
+
+ Component comp = ph.asMap().get("%x%");
+ assertThat(comp).isNotNull();
+ assertThat(comp.toString()).contains("Hello");
+ }
+
+ @Test
+ void builder_withObjectShouldConvertToStringComponent() {
+ AdventurePlaceholders ph = AdventurePlaceholders.builder()
+ .with("%n%", 123)
+ .build();
+
+ Component comp = ph.asMap().get("%n%");
+ assertThat(comp.toString()).contains("123");
+ }
+
+ @Test
+ void builder_withOtherPlaceholdersShouldMerge() {
+ AdventurePlaceholders base = AdventurePlaceholders.builder()
+ .with("%a%", "A")
+ .build();
+
+ AdventurePlaceholders merged = AdventurePlaceholders.builder()
+ .with("%b%", "B")
+ .with(base)
+ .build();
+
+ assertThat(merged.asMap())
+ .containsEntry("%a%", Component.text("A"))
+ .containsEntry("%b%", Component.text("B"));
+ }
+
+ @Test
+ void asMap_shouldBeUnmodifiable() {
+ AdventurePlaceholders ph = AdventurePlaceholders.builder()
+ .with("%x%", "1")
+ .build();
+
+ Map map = ph.asMap();
+
+ assertThrows(UnsupportedOperationException.class, () -> map.put("%y%", Component.text("2")));
+ }
+}
+
diff --git a/automessage-plugin/build.gradle.kts b/automessage-plugin/build.gradle.kts
new file mode 100644
index 0000000..9c5a9c1
--- /dev/null
+++ b/automessage-plugin/build.gradle.kts
@@ -0,0 +1,54 @@
+import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+
+plugins {
+ id("net.minecrell.plugin-yml.bukkit") version "0.6.0"
+ id("com.gradleup.shadow") version "9.2.1"
+}
+
+dependencies {
+ implementation(project(":automessage-core"))
+}
+
+tasks.build {
+ dependsOn(tasks.test)
+ dependsOn(tasks.shadowJar)
+}
+
+tasks.withType {
+ archiveFileName.set("AutoMessage v${project.version} (MC 1.21.x).jar")
+
+ mergeServiceFiles()
+
+ exclude(
+ "META-INF/*.SF",
+ "META-INF/*.DSA",
+ "META-INF/*.RSA",
+ "module-info.class",
+ "org/intellij/lang/annotations/**",
+ "org/jetbrains/annotations/**"
+ )
+
+ val relocationPrefix = "com.github.imdmk.automessage.lib"
+ listOf(
+ "com.eternalcode.multification",
+ "dev.rollczi.litecommands",
+ "eu.okaeri.configs",
+ "net.kyori",
+ "org.bstats",
+ "org.yaml.snakeyaml",
+ ).forEach { pkg ->
+ relocate(pkg, "$relocationPrefix.$pkg")
+ }
+
+ minimize()
+}
+
+bukkit {
+ name = "AutoMessage"
+ version = project.version.toString()
+ apiVersion = "1.21"
+ main = "com.github.imdmk.automessage.AutoMessagePluginLoader"
+ author = "imDMK (dominiks8318@gmail.com)"
+ description = "High-performance plugin for fully customizable automatic server-wide broadcasts."
+ website = "https://github.com/imDMK/AutoMessage"
+}
\ No newline at end of file
diff --git a/automessage-plugin/src/main/java/com/github/imdmk/automessage/AutoMessagePluginLoader.java b/automessage-plugin/src/main/java/com/github/imdmk/automessage/AutoMessagePluginLoader.java
new file mode 100644
index 0000000..35128ea
--- /dev/null
+++ b/automessage-plugin/src/main/java/com/github/imdmk/automessage/AutoMessagePluginLoader.java
@@ -0,0 +1,25 @@
+package com.github.imdmk.automessage;
+
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.java.JavaPlugin;
+
+public final class AutoMessagePluginLoader extends JavaPlugin {
+
+ private volatile AutoMessagePlugin pluginCore;
+
+ @Override
+ public void onEnable() {
+ final Plugin plugin = this;
+
+ pluginCore = new AutoMessagePlugin(plugin);
+ pluginCore.enable(new DefaultPluginSettings());
+ }
+
+ @Override
+ public void onDisable() {
+ if (pluginCore != null) {
+ pluginCore.disable();
+ pluginCore = null;
+ }
+ }
+}
diff --git a/automessage-plugin/src/main/java/com/github/imdmk/automessage/DefaultPluginSettings.java b/automessage-plugin/src/main/java/com/github/imdmk/automessage/DefaultPluginSettings.java
new file mode 100644
index 0000000..2a30160
--- /dev/null
+++ b/automessage-plugin/src/main/java/com/github/imdmk/automessage/DefaultPluginSettings.java
@@ -0,0 +1,20 @@
+package com.github.imdmk.automessage;
+
+import com.github.imdmk.automessage.config.ConfigSection;
+import com.github.imdmk.automessage.scheduled.ScheduledMessagesConfig;
+import com.github.imdmk.automessage.scheduled.dispatcher.MessageDispatcherConfig;
+import com.github.imdmk.automessage.shared.message.MessageConfig;
+
+import java.util.List;
+
+public final class DefaultPluginSettings implements PluginSettings {
+
+ @Override
+ public List> configs() {
+ return List.of(
+ MessageConfig.class,
+ ScheduledMessagesConfig.class,
+ MessageDispatcherConfig.class
+ );
+ }
+}
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index ba3a22b..0000000
--- a/build.gradle
+++ /dev/null
@@ -1,95 +0,0 @@
-plugins {
- id("java-library")
-
- id("com.github.johnrengelman.shadow") version "8.1.1"
- id("net.minecrell.plugin-yml.bukkit") version "0.6.0"
- id("checkstyle")
-}
-
-group = "com.github.imdmk.automessage"
-version = "1.0.4"
-
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
-
-java {
- sourceCompatibility = JavaVersion.VERSION_17
- targetCompatibility = JavaVersion.VERSION_17
-}
-
-repositories {
- mavenCentral()
-
- maven { url = "https://hub.spigotmc.org/nexus/content/repositories/snapshots/" }
- maven { url = "https://storehouse.okaeri.eu/repository/maven-public/" }
- maven { url = "https://repo.panda-lang.org/releases" }
- maven { url = "https://repo.eternalcode.pl/releases" }
-}
-
-dependencies {
- compileOnly("org.spigotmc:spigot-api:1.17-R0.1-SNAPSHOT")
-
- implementation("eu.okaeri:okaeri-configs-yaml-snakeyaml:5.0.9")
- implementation("eu.okaeri:okaeri-configs-serdes-commons:5.0.9")
-
- implementation("net.kyori:adventure-platform-bukkit:4.4.0")
- implementation("net.kyori:adventure-text-minimessage:4.23.0")
-
- implementation("dev.rollczi:litecommands-bukkit:3.10.6")
- implementation("dev.rollczi:litecommands-annotations:3.10.6")
-
- implementation("com.eternalcode:multification-bukkit:1.2.1")
- implementation("com.eternalcode:multification-okaeri:1.2.1")
- implementation("com.eternalcode:gitcheck:1.0.0")
-
- implementation("org.bstats:bstats-bukkit:3.1.0")
-
- testImplementation platform("org.junit:junit-bom:5.13.2")
- testImplementation("org.junit.jupiter:junit-jupiter:5.13.2")
-}
-
-bukkit {
- name = "AutoMessage"
- version = "${project.version}"
- apiVersion = "1.17"
- main = "com.github.imdmk.automessage.AutoMessagePlugin"
- author = "imDMK"
- description = "Advanced plugin for sending automatic chat, bossbar, and title messages to players."
- website = "https://github.com/imDMK/AutoMessage"
-}
-
-test {
- useJUnitPlatform()
-}
-
-checkstyle {
- toolVersion = "10.21.0"
- configFile = file("checkstyle.xml")
-}
-
-shadowJar {
- archiveFileName.set("${project.name} v${project.version}.jar")
-
- dependsOn("checkstyleMain")
- dependsOn("checkstyleTest")
- dependsOn("test")
-
- exclude(
- "org/intellij/lang/annotations/**",
- "org/jetbrains/annotations/**",
- "META-INF/**",
- )
-
- def prefix = "com.github.imdmk.automessage.lib"
- [
- "net.kyori",
- "dev.rollczi",
- "org.yaml",
- "org.bstats",
- "org.json",
- "com.eternalcode",
- "eu.okaeri",
- ].each { lib ->
- relocate(lib, "${prefix}.${lib}")
- }
-}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..5b1a37a
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,38 @@
+group = "com.github.imdmk.automessage"
+version = "2.0.0"
+
+subprojects {
+ version = "2.0.0"
+ apply(plugin = "java-library")
+
+ repositories {
+ mavenCentral()
+ maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") // Spigot
+ maven("https://repo.eternalcode.pl/releases") // Eternalcode
+ maven("https://storehouse.okaeri.eu/repository/maven-public/") // Okaeri
+ maven("https://repo.panda-lang.org/releases") // Litecommands
+ }
+
+ extensions.configure {
+ toolchain.languageVersion.set(JavaLanguageVersion.of(21))
+ withJavadocJar()
+ withSourcesJar()
+ }
+
+ tasks.withType().configureEach {
+ isFailOnError = false
+
+ val opts = options as StandardJavadocDocletOptions
+ opts.addStringOption("Xdoclint:none", "-quiet")
+ }
+
+ tasks.withType().configureEach {
+ useJUnitPlatform()
+ }
+
+ tasks.withType().configureEach {
+ options.compilerArgs.addAll(listOf("-Xlint:deprecation", "-Xlint:unchecked", "-parameters"))
+ options.encoding = "UTF-8"
+ options.release.set(21)
+ }
+}
\ No newline at end of file
diff --git a/checkstyle.xml b/checkstyle.xml
deleted file mode 100644
index b46f726..0000000
--- a/checkstyle.xml
+++ /dev/null
@@ -1,206 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 857f2a6..a0b9d8b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,15 @@
-#Fri Apr 04 19:27:25 CEST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
\ No newline at end of file
+zipStorePath=wrapper/dists
+
+org.gradle.daemon=true
+org.gradle.parallel=true
+org.gradle.configureondemand=true
+org.gradle.caching=true
+org.gradle.vfs.watch=true
+
+org.gradle.configuration-cache=false
+
+systemProp.file.encoding=UTF-8
diff --git a/gradlew b/gradlew
index 1b6c787..a58591e 100644
--- a/gradlew
+++ b/gradlew
@@ -231,4 +231,4 @@ eval "set -- $(
tr '\n' ' '
)" '"$@"'
-exec "$JAVACMD" "$@"
+exec "$JAVACMD" "$@"
\ No newline at end of file
diff --git a/gradlew.bat b/gradlew.bat
index 107acd3..477c896 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -86,4 +86,4 @@ exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
-:omega
+:omega
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
deleted file mode 100644
index 532a25a..0000000
--- a/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-rootProject.name = 'AutoMessage'
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..bd3a62b
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,3 @@
+rootProject.name = "AutoMessage"
+include("automessage-core")
+include("automessage-plugin")
\ No newline at end of file
diff --git a/src/main/java/com/github/imdmk/automessage/AutoMessage.java b/src/main/java/com/github/imdmk/automessage/AutoMessage.java
deleted file mode 100644
index 3ee9f4b..0000000
--- a/src/main/java/com/github/imdmk/automessage/AutoMessage.java
+++ /dev/null
@@ -1,138 +0,0 @@
-package com.github.imdmk.automessage;
-
-import com.eternalcode.multification.notice.Notice;
-import com.github.imdmk.automessage.configuration.ConfigurationManager;
-import com.github.imdmk.automessage.configuration.PluginConfig;
-import com.github.imdmk.automessage.feature.command.builder.configuration.CommandConfig;
-import com.github.imdmk.automessage.feature.command.builder.configuration.CommandEditor;
-import com.github.imdmk.automessage.feature.command.builder.handler.MissingPermissionHandler;
-import com.github.imdmk.automessage.feature.command.builder.handler.UsageHandler;
-import com.github.imdmk.automessage.feature.command.builder.player.PlayerArgument;
-import com.github.imdmk.automessage.feature.command.builder.player.PlayerContextual;
-import com.github.imdmk.automessage.feature.command.implementation.DelayCommand;
-import com.github.imdmk.automessage.feature.command.implementation.DisableCommand;
-import com.github.imdmk.automessage.feature.command.implementation.DispatchCommand;
-import com.github.imdmk.automessage.feature.command.implementation.EnableCommand;
-import com.github.imdmk.automessage.feature.command.implementation.ReloadCommand;
-import com.github.imdmk.automessage.feature.message.MessageConfig;
-import com.github.imdmk.automessage.feature.message.MessageResultHandler;
-import com.github.imdmk.automessage.feature.message.MessageService;
-import com.github.imdmk.automessage.feature.message.auto.AutoMessageNotice;
-import com.github.imdmk.automessage.feature.message.auto.AutoMessageNoticeArgument;
-import com.github.imdmk.automessage.feature.message.auto.AutoMessageNoticeConfig;
-import com.github.imdmk.automessage.feature.message.auto.dispatcher.AutoMessageDispatcher;
-import com.github.imdmk.automessage.feature.message.auto.eligibility.AutoMessageEligibilityEvaluator;
-import com.github.imdmk.automessage.feature.message.auto.eligibility.DefaultEligibilityEvaluator;
-import com.github.imdmk.automessage.feature.update.UpdateController;
-import com.github.imdmk.automessage.feature.update.UpdateService;
-import com.github.imdmk.automessage.scheduler.BukkitTaskScheduler;
-import com.github.imdmk.automessage.scheduler.TaskScheduler;
-import com.google.common.base.Stopwatch;
-import dev.rollczi.litecommands.LiteCommands;
-import dev.rollczi.litecommands.bukkit.LiteBukkitFactory;
-import net.kyori.adventure.platform.bukkit.BukkitAudiences;
-import net.kyori.adventure.text.minimessage.MiniMessage;
-import org.bstats.bukkit.Metrics;
-import org.bukkit.Server;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-import org.bukkit.plugin.Plugin;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Objects;
-import java.util.concurrent.TimeUnit;
-import java.util.logging.Logger;
-import java.util.stream.Stream;
-
-/**
- * Core plugin initializer responsible for setting up configuration,
- * services, commands, schedulers, and external integrations.
- */
-class AutoMessage {
-
- private final Server server;
- private final Logger logger;
-
- private final ConfigurationManager configurationManager;
- private final MessageService messageService;
- private final TaskScheduler taskScheduler;
-
- private final LiteCommands liteCommands;
- private final Metrics metrics;
-
- AutoMessage(@NotNull Plugin plugin) {
- Objects.requireNonNull(plugin, "plugin cannot be null.");
- Stopwatch stopwatch = Stopwatch.createStarted();
-
- this.server = plugin.getServer();
- this.logger = plugin.getLogger();
-
- /* Configuration */
- this.configurationManager = new ConfigurationManager(this.logger, plugin.getDataFolder());
-
- PluginConfig pluginConfig = this.configurationManager.create(PluginConfig.class);
- MessageConfig messageConfig = this.configurationManager.create(MessageConfig.class);
- AutoMessageNoticeConfig autoMessageNoticeConfig = this.configurationManager.create(AutoMessageNoticeConfig.class);
- CommandConfig commandConfig = this.configurationManager.create(CommandConfig.class);
-
- /* Services */
- this.messageService = new MessageService(messageConfig, BukkitAudiences.create(plugin), MiniMessage.miniMessage());
- UpdateService updateService = new UpdateService(pluginConfig, plugin.getDescription());
-
- /* Scheduler */
- this.taskScheduler = new BukkitTaskScheduler(plugin, this.server);
-
- /* Dispatcher */
- AutoMessageEligibilityEvaluator eligibilityEvaluator = new DefaultEligibilityEvaluator();
-
- AutoMessageDispatcher autoMessageDispatcher = new AutoMessageDispatcher(this.server, this.configurationManager, autoMessageNoticeConfig, this.messageService, this.taskScheduler, eligibilityEvaluator);
- autoMessageDispatcher.schedule();
-
- /* Controllers */
- Stream.of(
- new UpdateController(this.logger, pluginConfig, this.messageService, updateService, this.taskScheduler)
- ).forEach(listener -> this.server.getPluginManager().registerEvents(listener, plugin));
-
- /* LiteCommands */
- this.liteCommands = LiteBukkitFactory.builder("AutoMessage", plugin, this.server)
- .argument(Player.class, new PlayerArgument(this.server, messageConfig))
- .argument(AutoMessageNotice.class, new AutoMessageNoticeArgument(messageConfig, autoMessageNoticeConfig))
-
- .context(Player.class, new PlayerContextual())
- .result(Notice.class, new MessageResultHandler(this.messageService))
-
- .missingPermission(new MissingPermissionHandler(this.messageService))
- .invalidUsage(new UsageHandler(this.messageService))
-
- .commands(
- new DelayCommand(this.messageService, autoMessageDispatcher),
- new DisableCommand(this.messageService, autoMessageDispatcher),
- new DispatchCommand(this.server, this.messageService, autoMessageDispatcher, eligibilityEvaluator),
- new EnableCommand(this.messageService, autoMessageDispatcher),
- new ReloadCommand(this.logger, this.configurationManager, this.messageService)
- )
-
- .editorGlobal(new CommandEditor(this.logger, commandConfig))
-
- .build();
-
- /* Metrics */
- this.metrics = new Metrics(plugin, AutoMessagePlugin.METRICS_SERVICE_ID);
-
- this.logger.info("Enabled plugin in " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + "ms.");
- }
-
- /**
- * Gracefully shuts down all plugin components and resources.
- * Called during plugin disable.
- */
- void disable() {
- this.configurationManager.shutdown();
- this.messageService.close();
- this.liteCommands.unregister();
- this.metrics.shutdown();
- this.taskScheduler.shutdown();
-
- this.logger.info("Successfully disabled plugin.");
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/AutoMessagePlugin.java b/src/main/java/com/github/imdmk/automessage/AutoMessagePlugin.java
deleted file mode 100644
index f3f45e2..0000000
--- a/src/main/java/com/github/imdmk/automessage/AutoMessagePlugin.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.github.imdmk.automessage;
-
-import org.bukkit.plugin.java.JavaPlugin;
-
-public class AutoMessagePlugin extends JavaPlugin {
-
- /** bStats Metrics service ID for reporting plugin statistics */
- public static final int METRICS_SERVICE_ID = 19487;
-
- private AutoMessage autoMessage;
-
- @Override
- public void onEnable() {
- this.autoMessage = new AutoMessage(this);
- }
-
- @Override
- public void onDisable() {
- this.autoMessage.disable();
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/configuration/ConfigSection.java b/src/main/java/com/github/imdmk/automessage/configuration/ConfigSection.java
deleted file mode 100644
index 1e8f5b0..0000000
--- a/src/main/java/com/github/imdmk/automessage/configuration/ConfigSection.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.github.imdmk.automessage.configuration;
-
-import eu.okaeri.configs.OkaeriConfig;
-import eu.okaeri.configs.serdes.OkaeriSerdesPack;
-import org.jetbrains.annotations.NotNull;
-
-public abstract class ConfigSection extends OkaeriConfig {
-
- public abstract @NotNull OkaeriSerdesPack getSerdesPack();
-
- public abstract @NotNull String getFileName();
-}
diff --git a/src/main/java/com/github/imdmk/automessage/configuration/ConfigurationLoadException.java b/src/main/java/com/github/imdmk/automessage/configuration/ConfigurationLoadException.java
deleted file mode 100644
index 3cb5d18..0000000
--- a/src/main/java/com/github/imdmk/automessage/configuration/ConfigurationLoadException.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.github.imdmk.automessage.configuration;
-
-public final class ConfigurationLoadException extends RuntimeException {
- public ConfigurationLoadException(Throwable cause) {
- super("Failed to load configuration", cause);
- }
-
- public ConfigurationLoadException(String message) {
- super(message);
- }
-
- public ConfigurationLoadException(String message, Throwable cause) {
- super(message, cause);
- }
-
-}
diff --git a/src/main/java/com/github/imdmk/automessage/configuration/ConfigurationManager.java b/src/main/java/com/github/imdmk/automessage/configuration/ConfigurationManager.java
deleted file mode 100644
index 3076d24..0000000
--- a/src/main/java/com/github/imdmk/automessage/configuration/ConfigurationManager.java
+++ /dev/null
@@ -1,107 +0,0 @@
-package com.github.imdmk.automessage.configuration;
-
-import eu.okaeri.configs.ConfigManager;
-import eu.okaeri.configs.OkaeriConfig;
-import eu.okaeri.configs.exception.OkaeriException;
-import eu.okaeri.configs.yaml.snakeyaml.YamlSnakeYamlConfigurer;
-import org.jetbrains.annotations.NotNull;
-import org.yaml.snakeyaml.DumperOptions;
-import org.yaml.snakeyaml.LoaderOptions;
-import org.yaml.snakeyaml.Yaml;
-import org.yaml.snakeyaml.constructor.Constructor;
-import org.yaml.snakeyaml.representer.Representer;
-import org.yaml.snakeyaml.resolver.Resolver;
-
-import java.io.File;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-public class ConfigurationManager {
-
- private final Set configs = ConcurrentHashMap.newKeySet();
-
- private final Logger logger;
- private final File dataFolder;
- private final ExecutorService executor;
-
- public ConfigurationManager(@NotNull Logger logger, @NotNull File dataFolder) {
- this.logger = Objects.requireNonNull(logger, "logger cannot be null");
- this.dataFolder = Objects.requireNonNull(dataFolder, "dataFolder cannot be null");
- this.executor = Executors.newSingleThreadExecutor();
- }
-
- public T create(@NotNull Class config) {
- T configFile = ConfigManager.create(config);
- File file = new File(this.dataFolder, configFile.getFileName());
-
- YamlSnakeYamlConfigurer yamlSnakeYamlConfigurer = this.createYamlSnakeYamlConfigurer();
-
- configFile.withConfigurer(yamlSnakeYamlConfigurer);
- configFile.withSerdesPack(configFile.getSerdesPack());
- configFile.withBindFile(file);
- configFile.withRemoveOrphans(true);
- configFile.saveDefaults();
- configFile.load(true);
-
- this.configs.add(configFile);
-
- return configFile;
- }
-
- private @NotNull YamlSnakeYamlConfigurer createYamlSnakeYamlConfigurer() {
- LoaderOptions loaderOptions = new LoaderOptions();
- Constructor constructor = new Constructor(loaderOptions);
-
- DumperOptions dumperOptions = new DumperOptions();
- dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.AUTO);
- dumperOptions.setIndent(2);
- dumperOptions.setSplitLines(false);
-
- Representer representer = new CustomRepresenter(dumperOptions);
- Resolver resolver = new Resolver();
-
- Yaml yaml = new Yaml(constructor, representer, dumperOptions, loaderOptions, resolver);
- return new YamlSnakeYamlConfigurer(yaml);
- }
-
- public @NotNull CompletableFuture reloadAll() {
- return CompletableFuture.runAsync(this::loadAll, this.executor);
- }
-
- private void loadAll() {
- this.configs.forEach(this::load);
- }
-
- public void load(@NotNull OkaeriConfig config) {
- try {
- config.load(true);
- }
- catch (OkaeriException exception) {
- this.logger.log(Level.SEVERE, "Failed to load config: " + config.getClass().getSimpleName(), exception);
- throw new ConfigurationLoadException(exception);
- }
- }
-
- public @NotNull CompletableFuture save(@NotNull OkaeriConfig config) {
- return CompletableFuture.runAsync(() -> {
- try {
- config.save();
- }
- catch (OkaeriException exception) {
- this.logger.log(Level.SEVERE, "Failed to save config: " + config.getClass().getSimpleName(), exception);
- throw new ConfigurationLoadException(exception);
- }
- }, this.executor);
- }
-
- public void shutdown() {
- this.logger.info("Shutting down ConfigurationManager executor");
- this.executor.shutdownNow();
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/configuration/PluginConfig.java b/src/main/java/com/github/imdmk/automessage/configuration/PluginConfig.java
deleted file mode 100644
index 524fe7c..0000000
--- a/src/main/java/com/github/imdmk/automessage/configuration/PluginConfig.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.github.imdmk.automessage.configuration;
-
-import eu.okaeri.configs.annotation.Comment;
-import eu.okaeri.configs.serdes.OkaeriSerdesPack;
-import eu.okaeri.configs.serdes.commons.SerdesCommons;
-import org.jetbrains.annotations.NotNull;
-
-import java.time.Duration;
-
-public class PluginConfig extends ConfigSection {
-
- @Comment("# Check for plugin update and send notification after administrator join to server?")
- public boolean checkUpdate = true;
-
- @Comment("# How often should the plugin check for updates? Recommended value: 1 day")
- public Duration updateInterval = Duration.ofDays(1);
-
- @Override
- public @NotNull OkaeriSerdesPack getSerdesPack() {
- return registry -> {
- registry.register(new SerdesCommons());
- };
- }
-
- @Override
- public @NotNull String getFileName() {
- return "pluginConfig.yml";
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/command/builder/configuration/Command.java b/src/main/java/com/github/imdmk/automessage/feature/command/builder/configuration/Command.java
deleted file mode 100644
index 738e2c0..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/command/builder/configuration/Command.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package com.github.imdmk.automessage.feature.command.builder.configuration;
-
-import eu.okaeri.configs.OkaeriConfig;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class Command extends OkaeriConfig {
-
- public String name;
-
- public boolean enabled = true;
-
- public List aliases = new ArrayList<>();
- public List permissions = new ArrayList<>();
-
- public Map subCommands = new HashMap<>();
-
- public Command() {}
-
- public Command(@NotNull String name) {
- this.name = name;
- }
-
- public Command(@NotNull String name, @NotNull List aliases) {
- this.name = name;
- this.aliases = aliases;
- }
-
- public Command(@NotNull String name, @NotNull List aliases, @NotNull List permissions) {
- this.name = name;
- this.aliases = aliases;
- this.permissions = permissions;
- }
-
- public Command(@NotNull String name, boolean enabled, @NotNull List aliases, @NotNull List permissions) {
- this.name = name;
- this.enabled = enabled;
- this.aliases = aliases;
- this.permissions = permissions;
- }
-
- public Command(
- @NotNull String name,
- boolean enabled,
- @NotNull List aliases,
- @NotNull List permissions,
- @NotNull Map subCommands
- ) {
- this.name = name;
- this.enabled = enabled;
- this.aliases = aliases;
- this.permissions = permissions;
- this.subCommands = subCommands;
- }
-
- public @NotNull String name() {
- return this.name;
- }
-
- public boolean isEnabled() {
- return this.enabled;
- }
-
- public @NotNull List aliases() {
- return Collections.unmodifiableList(this.aliases);
- }
-
- public @NotNull List permissions() {
- return Collections.unmodifiableList(this.permissions);
- }
-
- public @NotNull Map subCommands() {
- return Collections.unmodifiableMap(this.subCommands);
- }
-}
-
diff --git a/src/main/java/com/github/imdmk/automessage/feature/command/builder/configuration/CommandConfig.java b/src/main/java/com/github/imdmk/automessage/feature/command/builder/configuration/CommandConfig.java
deleted file mode 100644
index 861251f..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/command/builder/configuration/CommandConfig.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package com.github.imdmk.automessage.feature.command.builder.configuration;
-
-import com.github.imdmk.automessage.configuration.ConfigSection;
-import eu.okaeri.configs.annotation.Comment;
-import eu.okaeri.configs.annotation.Header;
-import eu.okaeri.configs.serdes.OkaeriSerdesPack;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-@Header({
- "#",
- "# This file allows you to configure commands.",
- "#"
-})
-public class CommandConfig extends ConfigSection {
-
- @Comment("# Specifies to enable command editor feature.")
- public boolean enabled = false;
-
- @Comment({
- "# This allows you to globally edit commands.",
- "# For example, if you want to change the command name from /my-furnaces to /furnaces,",
- "# you can configure it like this:",
- "#",
- "# commands:",
- "# :",
- "# name: \"\"",
- "# enabled: true/false",
- "# aliases:",
- "# - \"\"",
- "# permissions:",
- "# - \"\"",
- "# subCommands:",
- "# :",
- "# name: ",
- "# enabled: true/false",
- "# aliases:",
- "# - \"\"",
- "# permissions:",
- "# - \"\"",
- })
- public Map commands = Map.of(
- "automessage", new Command(
- "automessage",
- true,
- List.of("am"),
- List.of(),
- Map.of()
- )
- );
-
- public Optional getCommand(String name) {
- return Optional.ofNullable(this.commands.get(name));
- }
-
- @Override
- public @NotNull OkaeriSerdesPack getSerdesPack() {
- return registry -> {};
- }
-
- @Override
- public @NotNull String getFileName() {
- return "commandConfig.yml";
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/github/imdmk/automessage/feature/command/builder/configuration/CommandEditor.java b/src/main/java/com/github/imdmk/automessage/feature/command/builder/configuration/CommandEditor.java
deleted file mode 100644
index 1246581..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/command/builder/configuration/CommandEditor.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.github.imdmk.automessage.feature.command.builder.configuration;
-
-import dev.rollczi.litecommands.command.builder.CommandBuilder;
-import dev.rollczi.litecommands.editor.Editor;
-import dev.rollczi.litecommands.meta.Meta;
-import dev.rollczi.litecommands.permission.PermissionSet;
-import org.bukkit.command.CommandSender;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Map;
-import java.util.logging.Logger;
-
-public class CommandEditor implements Editor {
-
- private final Logger logger;
- private final CommandConfig configuration;
-
- public CommandEditor(@NotNull Logger logger, @NotNull CommandConfig configuration) {
- this.logger = logger;
- this.configuration = configuration;
- }
-
- @Override
- public CommandBuilder edit(CommandBuilder context) {
- if (!this.configuration.enabled) {
- return context;
- }
-
- return this.configuration.getCommand(context.name())
- .map(command -> {
- CommandBuilder updated = this.updateCommand(context, command);
- return this.updateSubCommand(updated, command.subCommands());
- })
- .map(command -> {
- this.logger.info("Edited command " + command.name() + " via configuration.");
- return command;
- })
- .orElse(context);
- }
-
- private @NotNull CommandBuilder updateCommand(@NotNull CommandBuilder context, @NotNull Command command) {
- return context
- .name(command.name())
- .aliases(command.aliases())
- .applyMeta(meta -> meta.list(Meta.PERMISSIONS, permissions -> permissions.add(new PermissionSet(command.permissions()))))
- .enabled(command.isEnabled());
- }
-
- private @NotNull CommandBuilder updateSubCommand(@NotNull CommandBuilder context, @NotNull Map subCommands) {
- for (Map.Entry entry : subCommands.entrySet()) {
- String id = entry.getKey();
- SubCommand sub = entry.getValue();
-
- context = context.editChild(id, child -> child
- .name(sub.name())
- .aliases(sub.aliases())
- .applyMeta(meta -> meta.list(Meta.PERMISSIONS, permissions -> permissions.addAll(new PermissionSet(sub.permissions()))))
- .enabled(sub.isEnabled()));
- }
-
- return context;
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/command/builder/configuration/SubCommand.java b/src/main/java/com/github/imdmk/automessage/feature/command/builder/configuration/SubCommand.java
deleted file mode 100644
index 6f64ead..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/command/builder/configuration/SubCommand.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.github.imdmk.automessage.feature.command.builder.configuration;
-
-import eu.okaeri.configs.OkaeriConfig;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-public class SubCommand extends OkaeriConfig {
-
- public String name;
- public boolean enabled;
-
- public List aliases = new ArrayList<>();
- public List permissions = new ArrayList<>();
-
- public SubCommand() {}
-
- public SubCommand(@NotNull String name, boolean enabled, @NotNull List aliases, @NotNull List permissions) {
- this.name = name;
- this.enabled = enabled;
- this.aliases = aliases;
- this.permissions = permissions;
- }
-
- public @NotNull String name() {
- return this.name;
- }
-
- public boolean isEnabled() {
- return this.enabled;
- }
-
- public @NotNull List aliases() {
- return Collections.unmodifiableList(this.aliases);
- }
-
- public @NotNull List permissions() {
- return Collections.unmodifiableList(this.permissions);
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/command/builder/handler/UsageHandler.java b/src/main/java/com/github/imdmk/automessage/feature/command/builder/handler/UsageHandler.java
deleted file mode 100644
index 87fc371..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/command/builder/handler/UsageHandler.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.github.imdmk.automessage.feature.command.builder.handler;
-
-import com.github.imdmk.automessage.feature.message.MessageService;
-import dev.rollczi.litecommands.handler.result.ResultHandlerChain;
-import dev.rollczi.litecommands.invalidusage.InvalidUsage;
-import dev.rollczi.litecommands.invalidusage.InvalidUsageHandler;
-import dev.rollczi.litecommands.invocation.Invocation;
-import dev.rollczi.litecommands.schematic.Schematic;
-import org.bukkit.command.CommandSender;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Objects;
-
-public class UsageHandler implements InvalidUsageHandler {
-
- private final MessageService messageService;
-
- public UsageHandler(@NotNull MessageService messageService) {
- this.messageService = Objects.requireNonNull(messageService, "message service cannot be null");
- }
-
- @Override
- public void handle(Invocation invocation, InvalidUsage result, ResultHandlerChain chain) {
- CommandSender sender = invocation.sender();
- Schematic schematic = result.getSchematic();
-
- if (schematic.isOnlyFirst()) {
- this.messageService.create()
- .viewer(sender)
- .notice(notice -> notice.invalidCommandUsage)
- .placeholder("{USAGE}", schematic.first())
- .send();
- return;
- }
-
- this.messageService.create()
- .viewer(sender)
- .notice(notice -> notice.usageHeader)
- .send();
-
- for (String scheme : schematic.all()) {
- this.messageService.create()
- .viewer(sender)
- .notice(notice -> notice.usageEntry)
- .placeholder("{USAGE}", scheme)
- .send();
- }
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/command/builder/player/PlayerArgument.java b/src/main/java/com/github/imdmk/automessage/feature/command/builder/player/PlayerArgument.java
deleted file mode 100644
index b5254ac..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/command/builder/player/PlayerArgument.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.github.imdmk.automessage.feature.command.builder.player;
-
-import com.github.imdmk.automessage.feature.message.MessageConfig;
-import dev.rollczi.litecommands.argument.Argument;
-import dev.rollczi.litecommands.argument.parser.ParseResult;
-import dev.rollczi.litecommands.argument.resolver.ArgumentResolver;
-import dev.rollczi.litecommands.invocation.Invocation;
-import dev.rollczi.litecommands.suggestion.SuggestionContext;
-import dev.rollczi.litecommands.suggestion.SuggestionResult;
-import org.bukkit.Server;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Objects;
-import java.util.Optional;
-
-public class PlayerArgument extends ArgumentResolver {
-
- private final Server server;
- private final MessageConfig messageConfig;
-
- public PlayerArgument(@NotNull Server server, @NotNull MessageConfig messageConfig) {
- this.server = Objects.requireNonNull(server, "server cannot be null");
- this.messageConfig = Objects.requireNonNull(messageConfig, "messageConfiguration cannot be null");
- }
-
- @Override
- protected ParseResult parse(Invocation invocation, Argument context, String argument) {
- return Optional.ofNullable(this.server.getPlayer(argument))
- .map(ParseResult::success)
- .orElseGet(() -> ParseResult.failure(this.messageConfig.playerNotFound));
- }
-
- @Override
- public SuggestionResult suggest(Invocation invocation, Argument argument, SuggestionContext context) {
- return this.server.getOnlinePlayers().stream()
- .map(Player::getName)
- .collect(SuggestionResult.collector());
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/command/builder/player/PlayerContextual.java b/src/main/java/com/github/imdmk/automessage/feature/command/builder/player/PlayerContextual.java
deleted file mode 100644
index d2e99d1..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/command/builder/player/PlayerContextual.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.github.imdmk.automessage.feature.command.builder.player;
-
-
-import dev.rollczi.litecommands.context.ContextProvider;
-import dev.rollczi.litecommands.context.ContextResult;
-import dev.rollczi.litecommands.invocation.Invocation;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-public class PlayerContextual implements ContextProvider {
-
- @Override
- public ContextResult provide(Invocation invocation) {
- if (invocation.sender() instanceof Player player) {
- return ContextResult.ok(() -> player);
- }
-
- return ContextResult.error("Only player can use this command.");
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/command/implementation/DelayCommand.java b/src/main/java/com/github/imdmk/automessage/feature/command/implementation/DelayCommand.java
deleted file mode 100644
index 54b5e65..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/command/implementation/DelayCommand.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.github.imdmk.automessage.feature.command.implementation;
-
-import com.github.imdmk.automessage.feature.message.MessageService;
-import com.github.imdmk.automessage.feature.message.auto.dispatcher.AutoMessageDispatcher;
-import com.github.imdmk.automessage.util.DurationUtil;
-import dev.rollczi.litecommands.annotations.argument.Arg;
-import dev.rollczi.litecommands.annotations.command.Command;
-import dev.rollczi.litecommands.annotations.context.Context;
-import dev.rollczi.litecommands.annotations.execute.Execute;
-import dev.rollczi.litecommands.annotations.permission.Permission;
-import org.bukkit.command.CommandSender;
-import org.jetbrains.annotations.NotNull;
-
-import java.time.Duration;
-import java.util.Objects;
-
-@Command(name = "automessage delay")
-@Permission("command.automessage.delay")
-public class DelayCommand {
-
- private final MessageService messageService;
- private final AutoMessageDispatcher autoMessageDispatcher;
-
- public DelayCommand(
- @NotNull MessageService messageService,
- @NotNull AutoMessageDispatcher autoMessageDispatcher
- ) {
- this.messageService = Objects.requireNonNull(Objects.requireNonNull(messageService, "messageService cannot be null"));
- this.autoMessageDispatcher = Objects.requireNonNull(autoMessageDispatcher, "autoMessageDispatcher cannot be null");
- }
-
- @Execute
- void show(@Context CommandSender sender) {
- this.messageService.create()
- .viewer(sender)
- .notice(notice -> notice.autoMessageDelay)
- .placeholder("{DELAY}", DurationUtil.format(this.autoMessageDispatcher.getDelay()))
- .send();
- }
-
- @Execute(name = "set")
- void set(@Context CommandSender sender, @Arg Duration delay) {
- this.autoMessageDispatcher.changeDelay(DurationUtil.toTicks(delay));
-
- this.messageService.create()
- .viewer(sender)
- .notice(notice -> notice.autoMessageDelayChange)
- .placeholder("{DELAY}", DurationUtil.format(delay))
- .send();
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/command/implementation/DisableCommand.java b/src/main/java/com/github/imdmk/automessage/feature/command/implementation/DisableCommand.java
deleted file mode 100644
index a66cb10..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/command/implementation/DisableCommand.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.github.imdmk.automessage.feature.command.implementation;
-
-import com.github.imdmk.automessage.feature.message.MessageService;
-import com.github.imdmk.automessage.feature.message.auto.dispatcher.AutoMessageDispatcher;
-import dev.rollczi.litecommands.annotations.command.Command;
-import dev.rollczi.litecommands.annotations.context.Context;
-import dev.rollczi.litecommands.annotations.execute.Execute;
-import dev.rollczi.litecommands.annotations.permission.Permission;
-import org.bukkit.command.CommandSender;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Objects;
-
-@Command(name = "automessage disable")
-@Permission("command.automessage.disable")
-public class DisableCommand {
-
- private final MessageService messageService;
- private final AutoMessageDispatcher dispatcher;
-
- public DisableCommand(@NotNull MessageService messageService, @NotNull AutoMessageDispatcher dispatcher) {
- this.messageService = Objects.requireNonNull(messageService, "messageService cannot be null");
- this.dispatcher = Objects.requireNonNull(dispatcher, "dispatcher cannot be null");
- }
-
- @Execute
- void enable(@Context CommandSender sender) {
- if (!this.dispatcher.isEnabled()) {
- this.messageService.send(sender, notice -> notice.autoMessageAlreadyDisabled);
- return;
- }
-
- this.dispatcher.setEnabled(false);
- this.dispatcher.cancel(); // Cancel task
-
- this.messageService.send(sender, notice -> notice.autoMessageDisable);
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/command/implementation/DispatchCommand.java b/src/main/java/com/github/imdmk/automessage/feature/command/implementation/DispatchCommand.java
deleted file mode 100644
index f737a33..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/command/implementation/DispatchCommand.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package com.github.imdmk.automessage.feature.command.implementation;
-
-import com.github.imdmk.automessage.feature.message.MessageService;
-import com.github.imdmk.automessage.feature.message.auto.AutoMessageNotice;
-import com.github.imdmk.automessage.feature.message.auto.dispatcher.AutoMessageDispatcher;
-import com.github.imdmk.automessage.feature.message.auto.eligibility.AutoMessageEligibilityEvaluator;
-import com.github.imdmk.automessage.feature.message.auto.selector.AutoMessageSelector;
-import com.github.imdmk.automessage.feature.message.auto.selector.AutoMessageSelectorFactory;
-import com.github.imdmk.automessage.feature.message.auto.selector.AutoMessageSelectorMode;
-import dev.rollczi.litecommands.annotations.argument.Arg;
-import dev.rollczi.litecommands.annotations.command.Command;
-import dev.rollczi.litecommands.annotations.context.Context;
-import dev.rollczi.litecommands.annotations.execute.Execute;
-import dev.rollczi.litecommands.annotations.permission.Permission;
-import org.bukkit.Server;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Objects;
-
-@Command(name = "automessage dispatch")
-@Permission("command.automessage.dispatch")
-public class DispatchCommand {
-
- private final Server server;
- private final MessageService messageService;
- private final AutoMessageDispatcher dispatcher;
- private final AutoMessageEligibilityEvaluator evaluator;
- private final AutoMessageSelector randomSelector;
-
- public DispatchCommand(
- @NotNull Server server,
- @NotNull MessageService messageService,
- @NotNull AutoMessageDispatcher dispatcher,
- @NotNull AutoMessageEligibilityEvaluator evaluator
- ) {
- this.server = Objects.requireNonNull(server, "server cannot be null");
- this.messageService = Objects.requireNonNull(messageService, "messageService cannot be null");
- this.dispatcher = Objects.requireNonNull(dispatcher, "dispatcher cannot be null");
- this.evaluator = Objects.requireNonNull(evaluator, "evaluator cannot be null");
- this.randomSelector = AutoMessageSelectorFactory.create(AutoMessageSelectorMode.RANDOM, evaluator);
- }
-
- @Execute(name = "random")
- void random(@Context CommandSender sender) {
- this.server.getOnlinePlayers().forEach(player -> this.dispatcher.dispatch(player, this.randomSelector));
- this.messageService.send(sender, notice -> notice.autoMessageRandomDispatched);
- }
-
- @Execute(name = "random")
- void random(@Context CommandSender sender, @Arg Player player) {
- this.dispatcher.dispatch(player, this.randomSelector);
- this.messageService.send(sender, notice -> notice.autoMessageRandomDispatched);
- }
-
- @Execute(name = "select")
- void select(@Context CommandSender sender, @Arg AutoMessageNotice autoMessage) {
- this.server.getOnlinePlayers()
- .stream()
- .filter(player -> this.evaluator.canReceive(player, autoMessage))
- .forEach(player -> this.dispatcher.dispatch(player, autoMessage));
-
- this.messageService.send(sender, notice -> notice.autoMessageSelectedDispatched);
- }
-
- @Execute(name = "select")
- void select(@Context CommandSender sender, @Arg AutoMessageNotice autoMessage, @Arg Player player) {
- if (!this.evaluator.canReceive(player, autoMessage)) {
- this.messageService.send(sender, notice -> notice.autoMessageSelectedCannotReceive);
- return;
- }
-
- this.dispatcher.dispatch(player, autoMessage);
- this.messageService.send(sender, notice -> notice.autoMessageSelectedDispatched);
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/command/implementation/EnableCommand.java b/src/main/java/com/github/imdmk/automessage/feature/command/implementation/EnableCommand.java
deleted file mode 100644
index ad1ab59..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/command/implementation/EnableCommand.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.github.imdmk.automessage.feature.command.implementation;
-
-import com.github.imdmk.automessage.feature.message.MessageService;
-import com.github.imdmk.automessage.feature.message.auto.dispatcher.AutoMessageDispatcher;
-import dev.rollczi.litecommands.annotations.command.Command;
-import dev.rollczi.litecommands.annotations.context.Context;
-import dev.rollczi.litecommands.annotations.execute.Execute;
-import dev.rollczi.litecommands.annotations.permission.Permission;
-import org.bukkit.command.CommandSender;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Objects;
-
-@Command(name = "automessage enable")
-@Permission("command.automessage.enable")
-public class EnableCommand {
-
- private final MessageService messageService;
- private final AutoMessageDispatcher dispatcher;
-
- public EnableCommand(@NotNull MessageService messageService, @NotNull AutoMessageDispatcher dispatcher) {
- this.messageService = Objects.requireNonNull(messageService, "messageService cannot be null");
- this.dispatcher = Objects.requireNonNull(dispatcher, "dispatcher cannot be null");
- }
-
- @Execute
- void enable(@Context CommandSender sender) {
- if (this.dispatcher.isEnabled()) {
- this.messageService.send(sender, notice -> notice.autoMessageAlreadyEnabled);
- return;
- }
-
- this.dispatcher.setEnabled(true);
- this.dispatcher.schedule(); // Reset task
-
- this.messageService.send(sender, notice -> notice.autoMessageEnable);
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/command/implementation/ReloadCommand.java b/src/main/java/com/github/imdmk/automessage/feature/command/implementation/ReloadCommand.java
deleted file mode 100644
index 47851de..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/command/implementation/ReloadCommand.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.github.imdmk.automessage.feature.command.implementation;
-
-import com.github.imdmk.automessage.configuration.ConfigurationManager;
-import com.github.imdmk.automessage.feature.message.MessageService;
-import dev.rollczi.litecommands.annotations.command.Command;
-import dev.rollczi.litecommands.annotations.context.Context;
-import dev.rollczi.litecommands.annotations.execute.Execute;
-import dev.rollczi.litecommands.annotations.permission.Permission;
-import org.bukkit.command.CommandSender;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Objects;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-@Command(name = "automessage reload")
-@Permission("command.automessage.reload")
-public class ReloadCommand {
-
- private final Logger logger;
- private final ConfigurationManager configurationManager;
- private final MessageService messageService;
-
- public ReloadCommand(
- @NotNull Logger logger,
- @NotNull ConfigurationManager configurationManager,
- @NotNull MessageService messageService
- ) {
- this.logger = Objects.requireNonNull(logger, "logger cannot be null");
- this.configurationManager = Objects.requireNonNull(configurationManager, "configurationManager cannot be null");
- this.messageService = Objects.requireNonNull(messageService, "messageService cannot be null");
- }
-
- @Execute
- void reload(@Context CommandSender sender) {
- this.configurationManager.reloadAll()
- .thenAccept(v -> this.messageService.send(sender, notice -> notice.reload))
- .exceptionally(throwable -> {
- this.messageService.send(sender, notice -> notice.reloadError);
- this.logger.log(Level.SEVERE, "Failed to reload plugin configuration.", throwable);
- return null;
- });
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/MessageConfig.java b/src/main/java/com/github/imdmk/automessage/feature/message/MessageConfig.java
deleted file mode 100644
index 9681ac8..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/MessageConfig.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package com.github.imdmk.automessage.feature.message;
-
-import com.eternalcode.multification.notice.Notice;
-import com.eternalcode.multification.notice.resolver.NoticeResolverDefaults;
-import com.eternalcode.multification.okaeri.MultificationSerdesPack;
-import com.github.imdmk.automessage.configuration.ConfigSection;
-import eu.okaeri.configs.annotation.Comment;
-import eu.okaeri.configs.serdes.OkaeriSerdesPack;
-import org.jetbrains.annotations.NotNull;
-
-public class MessageConfig extends ConfigSection {
-
- @Comment({
- "# Sent when the automatic message delay is changed",
- "# {DELAY} - New delay value"
- })
- public Notice autoMessageDelayChange = Notice.chat("Changed auto message delay to {DELAY}.");
-
- @Comment({
- "# Sent when querying the current automatic message delay",
- "# {DELAY} - Current delay value"
- })
- public Notice autoMessageDelay = Notice.chat("Current auto message delay is {DELAY}.");
-
- @Comment("# Shown when auto messages are successfully enabled.")
- public Notice autoMessageEnable = Notice.chat("Enabled auto messages!");
-
- @Comment("# Shown when auto messages are already enabled.")
- public Notice autoMessageAlreadyEnabled = Notice.chat("Auto messages are currently enabled!");
-
- @Comment("# Shown when auto messages are successfully disabled.")
- public Notice autoMessageDisable = Notice.chat("Disabled auto messages!");
-
- @Comment("# Sent when automatic messages are already disabled")
- public Notice autoMessageAlreadyDisabled = Notice.chat("Auto messages are already disabled!");
-
- @Comment("# Sent when a random automatic message is dispatched")
- public Notice autoMessageRandomDispatched = Notice.chat("Dispatched a random auto message.");
-
- @Comment("# Sent when a selected automatic message is dispatched")
- public Notice autoMessageSelectedDispatched = Notice.chat("Dispatched a selected auto message.");
-
- @Comment({
- "# Sent when the selected target player cannot receive the selected message",
- "# Typically due to insufficient group or permission as defined in the configuration"
- })
- public Notice autoMessageSelectedCannotReceive = Notice.chat("Selected player cannot receive the selected auto message (e.g. missing required group or permission).");
-
- @Comment("# Sent when the provided auto message name does not exist")
- public Notice autoMessageNotFound = Notice.chat("Auto message with the given name not found.");
-
- @Comment({
- "# Sent when a command is used incorrectly",
- "# {USAGE} - Correct command usage"
- })
- public Notice invalidCommandUsage = Notice.chat("Invalid usage: {USAGE}");
-
- @Comment("# Header for multiple valid usages of a command")
- public Notice usageHeader = Notice.chat("Invalid usage:");
-
- @Comment({
- "# Entry in the list of valid usages",
- "# {USAGE} - Correct command usage"
- })
- public Notice usageEntry = Notice.chat("- {USAGE}");
-
- @Comment({
- "# Sent when command sender lacks required permissions",
- "# {PERMISSIONS} - Required permission nodes"
- })
- public Notice missingPermissions = Notice.chat("Missing permissions: {PERMISSIONS}");
-
- @Comment("# Sent when the player could not be found")
- public Notice playerNotFound = Notice.chat("Player not found.");
-
- @Comment("# Sent when successfully reloaded all plugin configuration files")
- public Notice reload = Notice.chat("The plugin configuration files has been reloaded. May note that not all functions are reloaded.");
-
- @Comment("# Sent when there is an error loading plugin configuration files")
- public Notice reloadError = Notice.chat("Failed to reload plugin configuration files. Please see the console.");
-
- @Override
- public @NotNull OkaeriSerdesPack getSerdesPack() {
- return registry -> {
- registry.register(new MultificationSerdesPack(NoticeResolverDefaults.createRegistry()));
- };
- }
-
- @Override
- public @NotNull String getFileName() {
- return "messageConfig.yml";
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/MessageResultHandler.java b/src/main/java/com/github/imdmk/automessage/feature/message/MessageResultHandler.java
deleted file mode 100644
index 3275096..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/MessageResultHandler.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.github.imdmk.automessage.feature.message;
-
-import com.eternalcode.multification.notice.Notice;
-import dev.rollczi.litecommands.handler.result.ResultHandler;
-import dev.rollczi.litecommands.handler.result.ResultHandlerChain;
-import dev.rollczi.litecommands.invocation.Invocation;
-import org.bukkit.command.CommandSender;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Objects;
-
-public final class MessageResultHandler implements ResultHandler {
-
- private final MessageService messageService;
-
- public MessageResultHandler(@NotNull MessageService messageService) {
- this.messageService = Objects.requireNonNull(messageService, "messageService cannot be null");
- }
-
- @Override
- public void handle(Invocation invocation, Notice result, ResultHandlerChain chain) {
- this.messageService.create()
- .viewer(invocation.sender())
- .notice(result)
- .send();
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/MessageService.java b/src/main/java/com/github/imdmk/automessage/feature/message/MessageService.java
deleted file mode 100644
index 4e3aff8..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/MessageService.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.github.imdmk.automessage.feature.message;
-
-import com.eternalcode.multification.adventure.AudienceConverter;
-import com.eternalcode.multification.bukkit.BukkitMultification;
-import com.eternalcode.multification.notice.Notice;
-import com.eternalcode.multification.notice.provider.NoticeProvider;
-import com.eternalcode.multification.translation.TranslationProvider;
-import net.kyori.adventure.platform.AudienceProvider;
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.minimessage.MiniMessage;
-import net.kyori.adventure.text.serializer.ComponentSerializer;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Objects;
-
-public final class MessageService extends BukkitMultification {
-
- private final MessageConfig messageConfig;
- private final AudienceProvider audienceProvider;
- private final MiniMessage miniMessage;
-
- public MessageService(
- @NotNull MessageConfig messageConfig,
- @NotNull AudienceProvider audienceProvider,
- @NotNull MiniMessage miniMessage
- ) {
- this.messageConfig = Objects.requireNonNull(messageConfig, "messageConfiguration cannot be null");
- this.audienceProvider = Objects.requireNonNull(audienceProvider, "audienceProvider cannot be null");
- this.miniMessage = Objects.requireNonNull(miniMessage, "miniMessage cannot be null");
- }
-
- @Override
- protected @NotNull TranslationProvider translationProvider() {
- return locale -> this.messageConfig;
- }
-
- @Override
- protected @NotNull ComponentSerializer serializer() {
- return this.miniMessage;
- }
-
- @Override
- protected @NotNull AudienceConverter audienceConverter() {
- return commandSender -> {
- if (commandSender instanceof Player player) {
- return this.audienceProvider.player(player.getUniqueId());
- }
-
- return this.audienceProvider.console();
- };
- }
-
- public void send(@NotNull CommandSender sender, @NotNull NoticeProvider notice) {
- this.create().viewer(sender).notice(notice).send();
- }
-
- public void send(@NotNull CommandSender sender, @NotNull Notice notice) {
- this.create().viewer(sender).notice(notice).send();
- }
-
- public void sendAsync(@NotNull CommandSender sender, @NotNull Notice notice) {
- this.create().viewer(sender).notice(notice).sendAsync();
- }
-
- public void close() {
- this.audienceProvider.close();
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/auto/AutoMessageNotice.java b/src/main/java/com/github/imdmk/automessage/feature/message/auto/AutoMessageNotice.java
deleted file mode 100644
index 36e9885..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/auto/AutoMessageNotice.java
+++ /dev/null
@@ -1,174 +0,0 @@
-package com.github.imdmk.automessage.feature.message.auto;
-
-import com.eternalcode.multification.notice.Notice;
-import com.github.imdmk.automessage.feature.message.auto.sound.AutoMessageSound;
-import org.jetbrains.annotations.CheckReturnValue;
-import org.jetbrains.annotations.Contract;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.jetbrains.annotations.Unmodifiable;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-
-/**
- * Represents an auto-message notice with optional permission and group requirements.
- */
-public class AutoMessageNotice {
-
- private final @NotNull String name;
- private final @NotNull List notices;
-
- private final @Nullable AutoMessageSound sound;
- private final @Nullable String requiredPermission;
- private final @Nullable String requiredGroup;
-
- private final boolean ignoreAdmins;
-
- private AutoMessageNotice(
- @NotNull String name,
- @NotNull List notices,
- @Nullable AutoMessageSound sound,
- @Nullable String requiredPermission,
- @Nullable String requiredGroup,
- boolean ignoreAdmins
- ) {
- this.name = Objects.requireNonNull(name, "name cannot be null");
- this.notices = Objects.requireNonNull(notices, "notice cannot be null");
- this.sound = sound;
- this.requiredPermission = requiredPermission;
- this.requiredGroup = requiredGroup;
- this.ignoreAdmins = ignoreAdmins;
- }
-
- /**
- * @return the associated AutoMessageNotice name
- */
- public @NotNull String getName() {
- return this.name;
- }
-
- /**
- * @return the associated {@link Notice}
- */
- public @Unmodifiable List getNotices() {
- return Collections.unmodifiableList(this.notices);
- }
-
- /**
- * @return the associated {@link AutoMessageSound}
- */
- public Optional getSound() {
- return Optional.ofNullable(this.sound);
- }
-
- /**
- * @return optional required permission
- */
- public Optional getRequiredPermission() {
- return Optional.ofNullable(this.requiredPermission);
- }
-
- /**
- * @return optional required group
- */
- public Optional getRequiredGroup() {
- return Optional.ofNullable(this.requiredGroup);
- }
-
- /**
- * @return ignoreAdmins boolean
- */
- public boolean isIgnoreAdmins() {
- return this.ignoreAdmins;
- }
-
- /**
- * Creates a new builder instance.
- *
- * @return the builder
- */
- public static @NotNull Builder builder() {
- return new Builder();
- }
-
- /**
- * Builder for {@link AutoMessageNotice}.
- */
- public static class Builder {
-
- private String name;
- private List notices = new ArrayList<>();
-
- private AutoMessageSound sound;
- private String requiredPermission;
- private String requiredGroup;
- private boolean ignoreAdmins;
-
- @Contract("_ -> this")
- @CheckReturnValue
- public @NotNull Builder name(@NotNull String name) {
- Objects.requireNonNull(name, "name cannot be null");
- this.name = name;
- return this;
- }
-
- @Contract("_ -> this")
- @CheckReturnValue
- public @NotNull Builder notice(@NotNull Notice notice) {
- Objects.requireNonNull(notice, "notice cannot be null");
- this.notices.add(notice);
- return this;
- }
-
- @Contract("_ -> this")
- @CheckReturnValue
- public @NotNull Builder notices(@NotNull List notices) {
- Objects.requireNonNull(notices, "notices cannot be null");
- this.notices = notices;
- return this;
- }
-
- @Contract("_ -> this")
- @CheckReturnValue
- public @NotNull Builder sound(@Nullable AutoMessageSound sound) {
- this.sound = sound;
- return this;
- }
-
- @Contract("_ -> this")
- @CheckReturnValue
- public @NotNull Builder requiredPermission(@Nullable String requiredPermission) {
- this.requiredPermission = requiredPermission;
- return this;
- }
-
- @Contract("_ -> this")
- @CheckReturnValue
- public @NotNull Builder requiredGroup(@Nullable String requiredGroup) {
- this.requiredGroup = requiredGroup;
- return this;
- }
-
- @Contract("_ -> this")
- @CheckReturnValue
- public @NotNull Builder ignoreAdmins(boolean ignore) {
- this.ignoreAdmins = ignore;
- return this;
- }
-
- /**
- * Builds the {@link AutoMessageNotice} instance.
- *
- * @return new AutoMessageNotice instance
- * @throws IllegalStateException if notice is not set
- */
- @CheckReturnValue
- public @NotNull AutoMessageNotice build() {
- return new AutoMessageNotice(this.name, this.notices, this.sound, this.requiredPermission, this.requiredGroup, this.ignoreAdmins);
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/auto/AutoMessageNoticeArgument.java b/src/main/java/com/github/imdmk/automessage/feature/message/auto/AutoMessageNoticeArgument.java
deleted file mode 100644
index e97749f..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/auto/AutoMessageNoticeArgument.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.github.imdmk.automessage.feature.message.auto;
-
-import com.github.imdmk.automessage.feature.message.MessageConfig;
-import dev.rollczi.litecommands.argument.Argument;
-import dev.rollczi.litecommands.argument.parser.ParseResult;
-import dev.rollczi.litecommands.argument.resolver.ArgumentResolver;
-import dev.rollczi.litecommands.invocation.Invocation;
-import dev.rollczi.litecommands.suggestion.SuggestionContext;
-import dev.rollczi.litecommands.suggestion.SuggestionResult;
-import org.bukkit.command.CommandSender;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Objects;
-
-public class AutoMessageNoticeArgument extends ArgumentResolver {
-
- private final MessageConfig messageConfig;
- private final AutoMessageNoticeConfig noticeConfig;
-
- public AutoMessageNoticeArgument(@NotNull MessageConfig messageConfig, @NotNull AutoMessageNoticeConfig noticeConfig) {
- this.messageConfig = Objects.requireNonNull(messageConfig, "messageConfig cannot be null");
- this.noticeConfig = Objects.requireNonNull(noticeConfig, "noticeConfig cannot be null");
- }
-
- @Override
- protected ParseResult parse(Invocation invocation, Argument context, String argument) {
- return this.noticeConfig.getMessage(argument)
- .map(ParseResult::success)
- .orElseGet(() -> ParseResult.failure(this.messageConfig.autoMessageNotFound));
- }
-
- @Override
- public SuggestionResult suggest(Invocation invocation, Argument argument, SuggestionContext context) {
- return this.noticeConfig.messages.stream()
- .map(AutoMessageNotice::getName)
- .collect(SuggestionResult.collector());
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/auto/AutoMessageNoticeConfig.java b/src/main/java/com/github/imdmk/automessage/feature/message/auto/AutoMessageNoticeConfig.java
deleted file mode 100644
index 9bdcece..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/auto/AutoMessageNoticeConfig.java
+++ /dev/null
@@ -1,114 +0,0 @@
-package com.github.imdmk.automessage.feature.message.auto;
-
-import com.eternalcode.multification.notice.Notice;
-import com.eternalcode.multification.notice.resolver.NoticeResolverDefaults;
-import com.eternalcode.multification.okaeri.MultificationSerdesPack;
-import com.github.imdmk.automessage.configuration.ConfigSection;
-import com.github.imdmk.automessage.feature.message.auto.selector.AutoMessageSelectorMode;
-import com.github.imdmk.automessage.feature.message.auto.sound.AutoMessageSound;
-import com.github.imdmk.automessage.feature.message.auto.sound.AutoMessageSoundSerializer;
-import com.github.imdmk.automessage.feature.message.auto.sound.SoundSerializer;
-import eu.okaeri.configs.annotation.Comment;
-import eu.okaeri.configs.serdes.OkaeriSerdesPack;
-import eu.okaeri.configs.serdes.commons.SerdesCommons;
-import net.kyori.adventure.bossbar.BossBar;
-import org.bukkit.Sound;
-import org.jetbrains.annotations.NotNull;
-
-import java.time.Duration;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-
-public class AutoMessageNoticeConfig extends ConfigSection {
-
- @Comment("# How often should automatic messages be sent?")
- public Duration delay = Duration.ofSeconds(10);
-
- @Comment({
- "# Defines the selection strategy for automatic messages.",
- "# RANDOM - messages are chosen randomly.",
- "# SEQUENTIAL - messages are sent in order."
- })
- public AutoMessageSelectorMode mode = AutoMessageSelectorMode.SEQUENTIAL;
-
- @Comment({
- "# List of automatic messages to be dispatched.",
- "# Supports different Notice types like chat, actionbar, title, boss bar.",
- "# To make a new line in chat message, use \n"
- })
- public List messages = Arrays.asList(
- AutoMessageNotice.builder()
- .name("first-message")
- .notice(Notice.chat("[!] This is first announcement of automessage plugin!"))
- .sound(new AutoMessageSound(Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 1))
- .build(),
-
- AutoMessageNotice.builder()
- .name("second-message-actionbar")
- .notice(Notice.actionbar("[!] This is second announcement of automessage plugin!"))
- .build(),
-
- AutoMessageNotice.builder()
- .name("third-message-title")
- .notice(Notice.title("[!]", "This is third announcement!"))
- .build(),
-
- AutoMessageNotice.builder()
- .name("fourth-message-bossbar")
- .notice(Notice.bossBar(BossBar.Color.RED, BossBar.Overlay.PROGRESS, Duration.ofSeconds(5L), "[!] This is fourth announcement!"))
- .sound(new AutoMessageSound(Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 1))
- .build(),
-
- AutoMessageNotice.builder()
- .name("multiple-chat-actionbar")
- .notices(List.of(
- Notice.chat("[!] This is multiple announcements!"),
- Notice.actionbar("[!] This is multiple announcements!")
- ))
- .build(),
-
- AutoMessageNotice.builder()
- .name("only-vip-permission")
- .notice(Notice.chat("[!] This a announcement only for players with vip permission!"))
- .requiredPermission("vip")
- .ignoreAdmins(true)
- .build(),
-
- AutoMessageNotice.builder()
- .name("only-vip-group")
- .notice(Notice.chat("[!] This a announcement only for players with vip group!"))
- .requiredGroup("vip")
- .ignoreAdmins(true)
- .build()
- );
-
- @Override
- public @NotNull OkaeriSerdesPack getSerdesPack() {
- return registry -> {
- registry.register(new SerdesCommons());
- registry.register(new AutoMessageNoticeSerializer());
- registry.register(new MultificationSerdesPack(NoticeResolverDefaults.createRegistry()));
-
- registry.register(new AutoMessageSoundSerializer());
- registry.register(new SoundSerializer());
- };
- }
-
- @Override
- public @NotNull String getFileName() {
- return "autoMessageConfig.yml";
- }
-
- public Optional getMessage(@NotNull String name) {
- return this.messages.stream()
- .filter(notice -> notice.getName().equals(name))
- .findAny();
- }
-
- public void setDelay(@NotNull Duration delay) {
- Objects.requireNonNull(delay, "delay cannot be null");
- this.delay = delay;
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/auto/AutoMessageNoticeSerializer.java b/src/main/java/com/github/imdmk/automessage/feature/message/auto/AutoMessageNoticeSerializer.java
deleted file mode 100644
index 612308d..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/auto/AutoMessageNoticeSerializer.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.github.imdmk.automessage.feature.message.auto;
-
-import com.eternalcode.multification.notice.Notice;
-import com.github.imdmk.automessage.feature.message.auto.sound.AutoMessageSound;
-import eu.okaeri.configs.schema.GenericsDeclaration;
-import eu.okaeri.configs.serdes.DeserializationData;
-import eu.okaeri.configs.serdes.ObjectSerializer;
-import eu.okaeri.configs.serdes.SerializationData;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.List;
-import java.util.Optional;
-
-public class AutoMessageNoticeSerializer implements ObjectSerializer {
-
- @Override
- public boolean supports(@NotNull Class super AutoMessageNotice> type) {
- return AutoMessageNotice.class.isAssignableFrom(type);
- }
-
- @Override
- public void serialize(@NotNull AutoMessageNotice message, @NotNull SerializationData data, @NotNull GenericsDeclaration generics) {
- data.add("name", message.getName(), String.class);
- data.addCollection("notices", message.getNotices(), Notice.class);
-
- message.getSound().ifPresent(sound -> data.add("sound", sound, AutoMessageSound.class));
- message.getRequiredPermission().ifPresent(permission -> data.add("requiredPermission", permission, String.class));
- message.getRequiredGroup().ifPresent(group -> data.add("requiredGroup", group, String.class));
-
- if (message.isIgnoreAdmins()) {
- data.add("ignoreAdmins", message.isIgnoreAdmins(), Boolean.class);
- }
- }
-
- @Override
- public AutoMessageNotice deserialize(@NotNull DeserializationData data, @NotNull GenericsDeclaration generics) {
- String name = data.get("name", String.class);
- List notices = data.getAsList("notices", Notice.class);
-
- AutoMessageSound sound = data.get("sound", AutoMessageSound.class);
- String requiredPermission = data.get("requiredPermission", String.class);
- String requiredGroup = data.get("requiredGroup", String.class);
-
- boolean ignoreAdmins = Optional.ofNullable(data.get("ignoreAdmins", Boolean.class))
- .orElse(false);
-
- return AutoMessageNotice.builder()
- .name(name)
- .notices(notices)
- .sound(sound)
- .requiredPermission(requiredPermission)
- .requiredGroup(requiredGroup)
- .ignoreAdmins(ignoreAdmins)
- .build();
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/auto/dispatcher/AutoMessageDispatchTask.java b/src/main/java/com/github/imdmk/automessage/feature/message/auto/dispatcher/AutoMessageDispatchTask.java
deleted file mode 100644
index 425a16c..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/auto/dispatcher/AutoMessageDispatchTask.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.github.imdmk.automessage.feature.message.auto.dispatcher;
-
-import org.bukkit.Server;
-import org.bukkit.scheduler.BukkitRunnable;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Objects;
-
-/**
- * Scheduled task that periodically dispatches automatic messages.
- */
-final class AutoMessageDispatchTask extends BukkitRunnable {
-
- private final Server server;
- private final AutoMessageDispatcher dispatcher;
-
- public AutoMessageDispatchTask(@NotNull Server server, @NotNull AutoMessageDispatcher dispatcher) {
- this.server = Objects.requireNonNull(server, "server cannot be null");
- this.dispatcher = Objects.requireNonNull(dispatcher, "dispatcher cannot be null");
- }
-
- @Override
- public void run() {
- if (this.dispatcher.isEnabled()) {
- this.server.getOnlinePlayers().forEach(this.dispatcher::dispatch);
- }
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/auto/dispatcher/AutoMessageDispatcher.java b/src/main/java/com/github/imdmk/automessage/feature/message/auto/dispatcher/AutoMessageDispatcher.java
deleted file mode 100644
index 102ec7b..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/auto/dispatcher/AutoMessageDispatcher.java
+++ /dev/null
@@ -1,245 +0,0 @@
-package com.github.imdmk.automessage.feature.message.auto.dispatcher;
-
-import com.github.imdmk.automessage.configuration.ConfigurationManager;
-import com.github.imdmk.automessage.feature.message.MessageService;
-import com.github.imdmk.automessage.feature.message.auto.AutoMessageNotice;
-import com.github.imdmk.automessage.feature.message.auto.AutoMessageNoticeConfig;
-import com.github.imdmk.automessage.feature.message.auto.eligibility.AutoMessageEligibilityEvaluator;
-import com.github.imdmk.automessage.feature.message.auto.selector.AutoMessageSelector;
-import com.github.imdmk.automessage.feature.message.auto.selector.AutoMessageSelectorFactory;
-import com.github.imdmk.automessage.scheduler.TaskScheduler;
-import com.github.imdmk.automessage.util.DurationUtil;
-import org.bukkit.Server;
-import org.bukkit.entity.Player;
-import org.bukkit.scheduler.BukkitTask;
-import org.jetbrains.annotations.NotNull;
-
-import java.time.Duration;
-import java.util.Objects;
-
-/**
- * Handles dispatching of automatic messages to online players
- * based on configured strategy, eligibility, and scheduling.
- *
- * Messages are selected via {@link AutoMessageSelector} and sent using {@link MessageService}.
- */
-public final class AutoMessageDispatcher {
-
- /**
- * Delay in ticks before the first automatic message is dispatched.
- */
- private static final long INITIAL_DELAY_TICKS = 0L;
-
- /**
- * Indicates that no task is currently scheduled.
- */
- private static final int TASK_NOT_RUN_ID = 0;
-
- private final Server server;
- private final ConfigurationManager configurationManager;
- private final AutoMessageNoticeConfig configuration;
- private final MessageService messageService;
- private final TaskScheduler taskScheduler;
- private final AutoMessageSelector selector;
-
- /**
- * Delay between automatic messages, stored in server ticks.
- */
- private volatile long delayTicks;
-
- /**
- * ID of the currently scheduled Bukkit task; {@link #TASK_NOT_RUN_ID} if no task is scheduled.
- */
- private volatile int currentTask;
-
- /**
- * Indicates whether automatic message dispatching is enabled.
- */
- private volatile boolean enabled;
-
- /**
- * Constructs a new dispatcher using the given dependencies and selector factory.
- * Initializes delay based on configuration.
- *
- * @param server the Bukkit server instance
- * @param configurationManager the configuration manager used to persist delay updates
- * @param configuration the configuration section for auto messages
- * @param messageService the message service used to send messages
- * @param taskScheduler the scheduler used to manage dispatch tasks
- * @param eligibilityEvaluator evaluator to determine message eligibility
- */
- public AutoMessageDispatcher(
- @NotNull Server server,
- @NotNull ConfigurationManager configurationManager,
- @NotNull AutoMessageNoticeConfig configuration,
- @NotNull MessageService messageService,
- @NotNull TaskScheduler taskScheduler,
- @NotNull AutoMessageEligibilityEvaluator eligibilityEvaluator
- ) {
- this.server = Objects.requireNonNull(server, "server cannot be null");
- this.configurationManager = Objects.requireNonNull(configurationManager, "configurationManager cannot be null");
- this.configuration = Objects.requireNonNull(configuration, "configuration cannot be null");
- this.messageService = Objects.requireNonNull(messageService, "messageService cannot be null");
- this.taskScheduler = Objects.requireNonNull(taskScheduler, "taskScheduler cannot be null");
-
- this.selector = AutoMessageSelectorFactory.create(this.configuration.mode, eligibilityEvaluator);
- this.delayTicks = DurationUtil.toTicks(Objects.requireNonNull(this.configuration.delay, "delay cannot be null"));
- this.enabled = true;
- }
-
- /**
- * Full constructor used for restoring dispatcher state (e.g., from persistence).
- *
- * @param server the Bukkit server instance
- * @param configurationManager the configuration manager
- * @param configuration the auto message configuration
- * @param messageService the message service
- * @param taskScheduler the task scheduler
- * @param selector the message selector strategy
- * @param delayTicks the dispatch delay in ticks
- * @param currentTask the currently scheduled task ID
- * @param enabled whether dispatching is enabled
- */
- public AutoMessageDispatcher(
- @NotNull Server server,
- @NotNull ConfigurationManager configurationManager,
- @NotNull AutoMessageNoticeConfig configuration,
- @NotNull MessageService messageService,
- @NotNull TaskScheduler taskScheduler,
- @NotNull AutoMessageSelector selector,
- long delayTicks,
- int currentTask,
- boolean enabled
- ) {
- this.server = Objects.requireNonNull(server, "server cannot be null");
- this.configurationManager = Objects.requireNonNull(configurationManager, "configurationManager cannot be null");
- this.configuration = Objects.requireNonNull(configuration, "configuration cannot be null");
- this.messageService = Objects.requireNonNull(messageService, "messageService cannot be null");
- this.taskScheduler = Objects.requireNonNull(taskScheduler, "taskScheduler cannot be null");
- this.selector = Objects.requireNonNull(selector, "selector cannot be null");
- this.delayTicks = delayTicks;
- this.currentTask = currentTask;
- this.enabled = enabled;
- }
-
- /**
- * Dispatches an automatic message to the specified player if any eligible message is found.
- *
- * @param player the player to receive the message (must not be {@code null})
- */
- public void dispatch(@NotNull Player player) {
- this.dispatch(player, this.selector);
- }
-
- /**
- * Dispatches an automatic message using the given selector.
- *
- * @param player the player to receive the message
- * @param selector the selector used to choose a message
- */
- public void dispatch(@NotNull Player player, @NotNull AutoMessageSelector selector) {
- selector.selectFor(player, this.configuration.messages)
- .ifPresent(message -> this.dispatch(player, message));
- }
-
- /**
- * Sends the contents of the specified automatic message to the player.
- * This includes sounds and all notice components.
- *
- * @param player the player to receive the message
- * @param autoMessage the message to be dispatched
- */
- public void dispatch(@NotNull Player player, @NotNull AutoMessageNotice autoMessage) {
- autoMessage.getSound().ifPresent(sound -> sound.play(player));
- autoMessage.getNotices().forEach(notice -> this.messageService.sendAsync(player, notice));
- }
-
- /**
- * Updates the delay between automatic messages and reschedules the dispatch task.
- *
- * @param newDelayTicks new delay in ticks (must be > 0)
- * @throws IllegalArgumentException if {@code newDelayTicks} is less than or equal to zero
- */
- public void changeDelay(long newDelayTicks) {
- if (newDelayTicks <= 0) {
- throw new IllegalArgumentException("Delay must be positive");
- }
-
- if (this.delayTicks == newDelayTicks) {
- return;
- }
-
- this.configuration.setDelay(DurationUtil.fromTicks(newDelayTicks));
- this.configurationManager.save(this.configuration);
-
- this.delayTicks = newDelayTicks;
- this.schedule();
- }
-
- /**
- * Schedules or reschedules the repeating asynchronous task that dispatches messages
- * at intervals defined by {@link #delayTicks}. Cancels any previously scheduled task.
- *
- * This method is synchronized to prevent race conditions during rescheduling.
- */
- public synchronized void schedule() {
- if (this.isTaskScheduled()) {
- this.taskScheduler.cancelTask(this.currentTask);
- this.selector.reset();
- }
-
- BukkitTask task = this.taskScheduler.runTimerAsync(
- new AutoMessageDispatchTask(this.server, this), INITIAL_DELAY_TICKS, this.delayTicks
- );
- this.currentTask = task.getTaskId();
- }
-
- /**
- * Cancels the currently running auto message task, if any.
- * This method is synchronized to prevent concurrent modification.
- */
- public synchronized void cancel() {
- if (this.isTaskScheduled()) {
- this.taskScheduler.cancelTask(this.currentTask);
- this.selector.reset();
- }
- }
-
- /**
- * Returns the currently active delay between message dispatches.
- *
- * @return delay between dispatches as a {@link Duration}
- */
- @NotNull
- public Duration getDelay() {
- return DurationUtil.fromTicks(this.delayTicks);
- }
-
- /**
- * Returns whether a dispatch task is currently scheduled.
- *
- * @return {@code true} if a task is scheduled; {@code false} otherwise
- */
- public boolean isTaskScheduled() {
- return this.currentTask > TASK_NOT_RUN_ID;
- }
-
- /**
- * Enables or disables automatic dispatching of messages.
- * When disabled, tasks will continue to run, but no messages will be sent.
- *
- * @param enabled {@code true} to enable; {@code false} to disable
- */
- public void setEnabled(boolean enabled) {
- this.enabled = enabled;
- }
-
- /**
- * Returns whether automatic message dispatching is currently enabled.
- *
- * @return {@code true} if enabled; {@code false} otherwise
- */
- public boolean isEnabled() {
- return this.enabled;
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/auto/eligibility/AutoMessageEligibilityEvaluator.java b/src/main/java/com/github/imdmk/automessage/feature/message/auto/eligibility/AutoMessageEligibilityEvaluator.java
deleted file mode 100644
index ee62e6e..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/auto/eligibility/AutoMessageEligibilityEvaluator.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.github.imdmk.automessage.feature.message.auto.eligibility;
-
-import com.github.imdmk.automessage.feature.message.auto.AutoMessageNotice;
-import org.bukkit.entity.Player;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Evaluates whether a player is eligible to receive a specific auto message notice.
- */
-public interface AutoMessageEligibilityEvaluator {
-
- /**
- * Determines whether the given player is eligible to receive the specified auto message notice.
- *
- * @param player the player to evaluate, must not be {@code null}
- * @param notice the auto message notice to check eligibility against, must not be {@code null}
- * @return {@code true} if the player is eligible to receive the notice; {@code false} otherwise
- */
- boolean canReceive(@NotNull Player player, @NotNull AutoMessageNotice notice);
-
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/auto/eligibility/DefaultEligibilityEvaluator.java b/src/main/java/com/github/imdmk/automessage/feature/message/auto/eligibility/DefaultEligibilityEvaluator.java
deleted file mode 100644
index 117ae3e..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/auto/eligibility/DefaultEligibilityEvaluator.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.github.imdmk.automessage.feature.message.auto.eligibility;
-
-import com.github.imdmk.automessage.feature.message.auto.AutoMessageNotice;
-import org.bukkit.entity.Player;
-import org.jetbrains.annotations.NotNull;
-
-public final class DefaultEligibilityEvaluator implements AutoMessageEligibilityEvaluator {
-
- private static final String LUCK_PERMS_GROUP_PREFIX = "group.";
-
- @Override
- public boolean canReceive(@NotNull Player player, @NotNull AutoMessageNotice notice) {
- if (notice.isIgnoreAdmins() && player.isOp()) {
- return false;
- }
-
- return notice.getRequiredPermission()
- .map(player::hasPermission)
- .orElse(true)
- && notice.getRequiredGroup()
- .map(rank -> player.hasPermission(LUCK_PERMS_GROUP_PREFIX + rank))
- .orElse(true);
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/auto/selector/AutoMessageSelector.java b/src/main/java/com/github/imdmk/automessage/feature/message/auto/selector/AutoMessageSelector.java
deleted file mode 100644
index 55479b5..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/auto/selector/AutoMessageSelector.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.github.imdmk.automessage.feature.message.auto.selector;
-
-import com.github.imdmk.automessage.feature.message.auto.AutoMessageNotice;
-import org.bukkit.entity.Player;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.List;
-import java.util.Optional;
-
-/**
- * Selects an appropriate auto message notice for a player from a list of available messages.
- */
-public interface AutoMessageSelector {
-
- /**
- * Selects a suitable {@link AutoMessageNotice} for the given player based on the provided messages.
- *
- * @param player the player for whom to select the message, must not be {@code null}
- * @param messages the list of available messages to select from, must not be {@code null}
- * @return an {@link Optional} containing the selected auto message notice if any is applicable; otherwise, empty
- */
- Optional selectFor(@NotNull Player player, @NotNull List messages);
-
- /**
- * Resets any internal state maintained by the selector, preparing it for fresh selection cycles.
- */
- void reset();
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/auto/selector/AutoMessageSelectorFactory.java b/src/main/java/com/github/imdmk/automessage/feature/message/auto/selector/AutoMessageSelectorFactory.java
deleted file mode 100644
index ef9d3f6..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/auto/selector/AutoMessageSelectorFactory.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.github.imdmk.automessage.feature.message.auto.selector;
-
-import com.github.imdmk.automessage.feature.message.auto.eligibility.AutoMessageEligibilityEvaluator;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Factory for creating {@link AutoMessageSelector} instances based on the specified mode.
- */
-public final class AutoMessageSelectorFactory {
-
- private AutoMessageSelectorFactory() {
- throw new UnsupportedOperationException("Factory class cannot be instantiated.");
- }
-
- /**
- * Creates an {@link AutoMessageSelector} according to the given {@link AutoMessageSelectorMode}.
- *
- * @param mode the selection mode, must not be {@code null}
- * @param evaluator the eligibility evaluator used by the selector, must not be {@code null}
- * @return a new instance of {@link AutoMessageSelector} matching the specified mode
- */
- public static AutoMessageSelector create(
- @NotNull AutoMessageSelectorMode mode,
- @NotNull AutoMessageEligibilityEvaluator evaluator
- ) {
- return switch (mode) {
- case SEQUENTIAL -> new SequentialAutoMessageSelector(evaluator);
- case RANDOM -> new RandomAutoMessageSelector(evaluator);
- };
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/auto/selector/AutoMessageSelectorMode.java b/src/main/java/com/github/imdmk/automessage/feature/message/auto/selector/AutoMessageSelectorMode.java
deleted file mode 100644
index 7565ff1..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/auto/selector/AutoMessageSelectorMode.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.github.imdmk.automessage.feature.message.auto.selector;
-
-/**
- * Represents the strategy used for selecting and dispatching automatic messages.
- *
- * This enum defines how messages should be selected from the configured list.
- */
-public enum AutoMessageSelectorMode {
-
- /**
- * Messages are selected randomly from the configured list.
- * Each dispatch may return a different message, without any defined order.
- */
- RANDOM,
-
- /**
- * Messages are selected sequentially from the configured list.
- * Each dispatch returns the next message in order, cycling back to the start after the last one.
- */
- SEQUENTIAL
-
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/auto/selector/RandomAutoMessageSelector.java b/src/main/java/com/github/imdmk/automessage/feature/message/auto/selector/RandomAutoMessageSelector.java
deleted file mode 100644
index c6424ba..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/auto/selector/RandomAutoMessageSelector.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.github.imdmk.automessage.feature.message.auto.selector;
-
-import com.github.imdmk.automessage.feature.message.auto.AutoMessageNotice;
-import com.github.imdmk.automessage.feature.message.auto.eligibility.AutoMessageEligibilityEvaluator;
-import org.bukkit.entity.Player;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Random;
-
-final class RandomAutoMessageSelector implements AutoMessageSelector {
-
- private final AutoMessageEligibilityEvaluator evaluator;
-
- private final Random random = new Random();
-
- public RandomAutoMessageSelector(@NotNull AutoMessageEligibilityEvaluator evaluator) {
- this.evaluator = Objects.requireNonNull(evaluator, "evaluator cannot be null");
- }
-
- @Override
- public Optional selectFor(@NotNull Player player, @NotNull List messages) {
- List filtered = messages.stream()
- .filter(notice -> this.evaluator.canReceive(player, notice))
- .toList();
-
- if (filtered.isEmpty()) {
- return Optional.empty();
- }
-
- return Optional.of(filtered.get(this.random.nextInt(filtered.size())));
- }
-
- @Override
- public void reset() {}
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/auto/selector/SequentialAutoMessageSelector.java b/src/main/java/com/github/imdmk/automessage/feature/message/auto/selector/SequentialAutoMessageSelector.java
deleted file mode 100644
index 18c45e2..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/auto/selector/SequentialAutoMessageSelector.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.github.imdmk.automessage.feature.message.auto.selector;
-
-import com.github.imdmk.automessage.feature.message.auto.AutoMessageNotice;
-import com.github.imdmk.automessage.feature.message.auto.eligibility.AutoMessageEligibilityEvaluator;
-import org.bukkit.entity.Player;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.atomic.AtomicInteger;
-
-final class SequentialAutoMessageSelector implements AutoMessageSelector {
-
- private static final int INITIAL_POSITION = 0;
-
- private final AutoMessageEligibilityEvaluator evaluator;
- private final AtomicInteger position = new AtomicInteger(INITIAL_POSITION);
-
- public SequentialAutoMessageSelector(@NotNull AutoMessageEligibilityEvaluator evaluator) {
- this.evaluator = Objects.requireNonNull(evaluator, "evaluator cannot be null");
- }
-
- @Override
- public Optional selectFor(@NotNull Player player, @NotNull List messages) {
- int size = messages.size();
-
- for (int i = 0; i < size; i++) {
- int index = Math.floorMod(this.position.getAndIncrement(), size);;
-
- AutoMessageNotice candidate = messages.get(index);
- if (candidate == null) {
- return Optional.empty();
- }
-
- if (this.evaluator.canReceive(player, candidate)) {
- return Optional.of(candidate);
- }
- }
-
- return Optional.empty();
- }
-
- @Override
- public void reset() {
- this.position.set(INITIAL_POSITION);
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/auto/sound/AutoMessageSound.java b/src/main/java/com/github/imdmk/automessage/feature/message/auto/sound/AutoMessageSound.java
deleted file mode 100644
index 2dcdde3..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/auto/sound/AutoMessageSound.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.github.imdmk.automessage.feature.message.auto.sound;
-
-import org.bukkit.Sound;
-import org.bukkit.entity.Player;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Represents a sound effect configuration to be played during a jump.
- *
- * Wraps a {@link Sound} along with volume and pitch parameters
- *
- * @param sound the {@link Sound} to be played (must not be null)
- * @param volume the volume of the sound (typically between 0.0 and 1.0+)
- * @param pitch the pitch of the sound (typically between 0.5 and 2.0)
- */
-public record AutoMessageSound(@NotNull Sound sound, float volume, float pitch) {
-
- /**
- * Plays the configured sound effect for the specified player at their current location.
- *
- * @param player the {@link Player} to play the sound for (must not be null)
- */
- public void play(@NotNull Player player) {
- player.playSound(player.getLocation(), this.sound, this.volume, this.pitch);
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/auto/sound/AutoMessageSoundSerializer.java b/src/main/java/com/github/imdmk/automessage/feature/message/auto/sound/AutoMessageSoundSerializer.java
deleted file mode 100644
index 570287b..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/auto/sound/AutoMessageSoundSerializer.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.github.imdmk.automessage.feature.message.auto.sound;
-
-import eu.okaeri.configs.schema.GenericsDeclaration;
-import eu.okaeri.configs.serdes.DeserializationData;
-import eu.okaeri.configs.serdes.ObjectSerializer;
-import eu.okaeri.configs.serdes.SerializationData;
-import org.bukkit.Sound;
-import org.jetbrains.annotations.NotNull;
-
-public class AutoMessageSoundSerializer implements ObjectSerializer {
-
- @Override
- public boolean supports(@NotNull Class super AutoMessageSound> type) {
- return AutoMessageSound.class.isAssignableFrom(type);
- }
-
- @Override
- public void serialize(@NotNull AutoMessageSound autoSound, @NotNull SerializationData data, @NotNull GenericsDeclaration generics) {
- data.add("sound", autoSound.sound(), Sound.class);
- data.add("volume", autoSound.volume(), float.class);
- data.add("pitch", autoSound.pitch(), float.class);
- }
-
- @Override
- public AutoMessageSound deserialize(@NotNull DeserializationData data, @NotNull GenericsDeclaration generics) {
- Sound sound = data.get("sound", Sound.class);
- float volume = data.get("volume", float.class);
- float pitch = data.get("pitch", float.class);
-
- return new AutoMessageSound(sound, volume, pitch);
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/message/auto/sound/SoundSerializer.java b/src/main/java/com/github/imdmk/automessage/feature/message/auto/sound/SoundSerializer.java
deleted file mode 100644
index c0c6157..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/message/auto/sound/SoundSerializer.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.github.imdmk.automessage.feature.message.auto.sound;
-
-import eu.okaeri.configs.schema.GenericsDeclaration;
-import eu.okaeri.configs.serdes.DeserializationData;
-import eu.okaeri.configs.serdes.ObjectSerializer;
-import eu.okaeri.configs.serdes.SerializationData;
-import org.bukkit.Sound;
-import org.jetbrains.annotations.NotNull;
-
-public class SoundSerializer implements ObjectSerializer {
-
- @Override
- public boolean supports(@NotNull Class super Sound> type) {
- return Sound.class.isAssignableFrom(type);
- }
-
- @Override
- public void serialize(@NotNull Sound sound, @NotNull SerializationData data, @NotNull GenericsDeclaration generics) {
- data.setValue(sound.name(), String.class);
- }
-
- @Override
- public Sound deserialize(@NotNull DeserializationData data, @NotNull GenericsDeclaration generics) {
- return Sound.valueOf(data.getValue(String.class));
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/update/UpdateController.java b/src/main/java/com/github/imdmk/automessage/feature/update/UpdateController.java
deleted file mode 100644
index da7e2d7..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/update/UpdateController.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package com.github.imdmk.automessage.feature.update;
-
-import com.eternalcode.gitcheck.GitCheckResult;
-import com.eternalcode.gitcheck.git.GitException;
-import com.eternalcode.multification.notice.Notice;
-import com.github.imdmk.automessage.configuration.PluginConfig;
-import com.github.imdmk.automessage.feature.message.MessageService;
-import com.github.imdmk.automessage.scheduler.TaskScheduler;
-import com.github.imdmk.automessage.util.DurationUtil;
-import org.bukkit.entity.Player;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.EventPriority;
-import org.bukkit.event.Listener;
-import org.bukkit.event.player.PlayerJoinEvent;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Objects;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-public class UpdateController implements Listener {
-
- private static final String PREFIX = "[AutoMessage Plugin] ";
- private static final Notice UPDATE_AVAILABLE = Notice.chat(
- " ",
- PREFIX + "A new update is available!",
- "- We strongly recommend downloading it!",
- " "
- );
- private static final Notice UPDATE_EXCEPTION = Notice.chat(
- " ",
- PREFIX + "An error occurred while checking for plugin update! Next update check: {UPDATE_CHECK_INTERVAL}",
- " "
- );
-
- private final Logger logger;
- private final PluginConfig pluginConfig;
- private final MessageService messageService;
- private final UpdateService updateService;
- private final TaskScheduler taskScheduler;
-
- public UpdateController(
- @NotNull Logger logger,
- @NotNull PluginConfig pluginConfig,
- @NotNull MessageService messageService,
- @NotNull UpdateService updateService,
- @NotNull TaskScheduler taskScheduler
- ) {
- this.logger = Objects.requireNonNull(logger, "logger cannot be null");
- this.pluginConfig = Objects.requireNonNull(pluginConfig, "pluginConfiguration cannot be null");
- this.messageService = Objects.requireNonNull(messageService, "messageService cannot be null");
- this.updateService = Objects.requireNonNull(updateService, "updateService cannot be null");
- this.taskScheduler = Objects.requireNonNull(taskScheduler, "taskScheduler cannot be null");
- }
-
- @EventHandler(priority = EventPriority.HIGHEST)
- void onPlayerJoin(PlayerJoinEvent event) {
- if (!this.pluginConfig.checkUpdate) {
- return;
- }
-
- Player player = event.getPlayer();
-
- if (!player.isOp()) {
- return;
- }
-
- if (this.updateService.shouldCheck()) {
- this.taskScheduler.runAsync(() -> this.checkForUpdate(player));
- }
- }
-
- private void checkForUpdate(@NotNull Player player) {
- try {
- GitCheckResult result = this.updateService.check();
- if (result.isUpToDate()) {
- return;
- }
-
- this.sendNotice(player, UPDATE_AVAILABLE);
- }
- catch (GitException exception) {
- this.logger.log(Level.SEVERE, "An error occurred while checking for update", exception);
- this.sendNotice(player, UPDATE_EXCEPTION);
- }
- }
-
- private void sendNotice(@NotNull Player player, @NotNull Notice notice) {
- this.messageService.create()
- .notice(notice)
- .placeholder("{UPDATE_CHECK_INTERVAL}", DurationUtil.format(this.pluginConfig.updateInterval))
- .viewer(player)
- .send();
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/feature/update/UpdateService.java b/src/main/java/com/github/imdmk/automessage/feature/update/UpdateService.java
deleted file mode 100644
index e764d88..0000000
--- a/src/main/java/com/github/imdmk/automessage/feature/update/UpdateService.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.github.imdmk.automessage.feature.update;
-
-import com.eternalcode.gitcheck.GitCheck;
-import com.eternalcode.gitcheck.GitCheckResult;
-import com.eternalcode.gitcheck.git.GitRepository;
-import com.eternalcode.gitcheck.git.GitTag;
-import com.github.imdmk.automessage.configuration.PluginConfig;
-import org.bukkit.plugin.PluginDescriptionFile;
-import org.jetbrains.annotations.NotNull;
-
-import java.time.Instant;
-import java.util.Objects;
-
-public class UpdateService {
-
- private static final GitRepository GIT_REPOSITORY = GitRepository.of("imDMK", "AutoMessage");
- private static final GitCheck GIT_CHECK = new GitCheck();
-
- private final PluginConfig config;
- private final PluginDescriptionFile descriptionFile;
-
- private Instant latestCheck;
-
- public UpdateService(@NotNull PluginConfig config, @NotNull PluginDescriptionFile descriptionFile) {
- this.config = Objects.requireNonNull(config, "pluginConfiguration cannot be null");
- this.descriptionFile = Objects.requireNonNull(descriptionFile, "pluginDescriptionFile cannot be null");
- }
-
- public @NotNull GitCheckResult check() {
- this.latestCheck = Instant.now();
-
- GitTag tag = GitTag.of("v" + this.descriptionFile.getVersion());
- return GIT_CHECK.checkRelease(GIT_REPOSITORY, tag);
- }
-
- public boolean shouldCheck() {
- if (this.latestCheck == null) {
- return true;
- }
-
- Instant nextCheckTime = this.latestCheck.plus(this.config.updateInterval);
- return Instant.now().isAfter(nextCheckTime);
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/scheduler/BukkitTaskScheduler.java b/src/main/java/com/github/imdmk/automessage/scheduler/BukkitTaskScheduler.java
deleted file mode 100644
index 5fd12de..0000000
--- a/src/main/java/com/github/imdmk/automessage/scheduler/BukkitTaskScheduler.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.github.imdmk.automessage.scheduler;
-
-import org.bukkit.Server;
-import org.bukkit.plugin.Plugin;
-import org.bukkit.scheduler.BukkitScheduler;
-import org.bukkit.scheduler.BukkitTask;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Objects;
-
-public final class BukkitTaskScheduler implements TaskScheduler {
-
- private final Plugin plugin;
- private final BukkitScheduler bukkitScheduler;
-
- public BukkitTaskScheduler(@NotNull Plugin plugin, @NotNull Server server) {
- this.plugin = Objects.requireNonNull(plugin, "plugin cannot be null");
- this.bukkitScheduler = Objects.requireNonNull(server.getScheduler(), "server cannot be null");
- }
-
- @Override
- public BukkitTask runSync(@NotNull Runnable runnable) {
- Objects.requireNonNull(runnable, "runnable cannot be null");
- return this.bukkitScheduler.runTask(this.plugin, runnable);
- }
-
- @Override
- public BukkitTask runAsync(@NotNull Runnable runnable) {
- Objects.requireNonNull(runnable, "runnable cannot be null");
- return this.bukkitScheduler.runTaskAsynchronously(this.plugin, runnable);
- }
-
- @Override
- public BukkitTask runLaterAsync(@NotNull Runnable runnable, long delay) {
- Objects.requireNonNull(runnable, "runnable cannot be null");
- return this.bukkitScheduler.runTaskLaterAsynchronously(this.plugin, runnable, delay);
- }
-
- @Override
- public BukkitTask runTimerAsync(@NotNull Runnable runnable, long delay, long period) {
- Objects.requireNonNull(runnable, "runnable cannot be null");
- return this.bukkitScheduler.runTaskTimerAsynchronously(this.plugin, runnable, delay, period);
- }
-
- @Override
- public void cancelTask(int taskId) {
- this.bukkitScheduler.cancelTask(taskId);
- }
-
- @Override
- public void shutdown() {
- this.bukkitScheduler.cancelTasks(this.plugin);
- }
-}
diff --git a/src/main/java/com/github/imdmk/automessage/scheduler/TaskScheduler.java b/src/main/java/com/github/imdmk/automessage/scheduler/TaskScheduler.java
deleted file mode 100644
index 1f7e951..0000000
--- a/src/main/java/com/github/imdmk/automessage/scheduler/TaskScheduler.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package com.github.imdmk.automessage.scheduler;
-
-import org.bukkit.scheduler.BukkitTask;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Provides methods for scheduling tasks to be executed either synchronously on the main thread
- * or asynchronously on a separate thread.
- *
- * Implementations of this interface should ensure that synchronous tasks run on the main server thread,
- * while asynchronous tasks run off the main thread to prevent blocking.
- */
-public interface TaskScheduler {
-
- /**
- * Executes the given {@link Runnable} synchronously on the main server thread as soon as possible.
- *
- * @param runnable the task to run synchronously; must not be null
- * @throws NullPointerException if runnable is null
- */
- BukkitTask runSync(@NotNull Runnable runnable);
-
- /**
- * Executes the given {@link Runnable} asynchronously on a separate thread as soon as possible.
- *
- * @param runnable the task to run asynchronously; must not be null
- * @throws NullPointerException if runnable is null
- */
- BukkitTask runAsync(@NotNull Runnable runnable);
-
- /**
- * Executes the given {@link Runnable} asynchronously after the specified delay.
- * The delay unit depends on the implementation (typically server ticks or milliseconds).
- *
- * @param runnable the task to run asynchronously; must not be null
- * @param delay the delay before executing the task, in implementation-specific units (e.g. ticks)
- * @throws NullPointerException if runnable is null
- */
- BukkitTask runLaterAsync(@NotNull Runnable runnable, long delay);
-
- /**
- * Executes the given {@link Runnable} repeatedly on a timer asynchronously,
- * starting after the initial delay and repeating with the specified period.
- * The delay and period units depend on the implementation (e.g., server ticks or milliseconds).
- *
- * @param runnable the task to run asynchronously on a timer; must not be null
- * @param delay the initial delay before first execution, in implementation-specific units
- * @param period the period between later executions, in implementation-specific units
- * @throws NullPointerException if runnable is null
- */
- BukkitTask runTimerAsync(@NotNull Runnable runnable, long delay, long period);
-
- /**
- * Cancels a scheduled task identified by its task ID.
- *
- * If the task with the specified ID is currently scheduled or running, it will be cancelled,
- * preventing any future executions of the task. If no task with the given ID exists or
- * it has already completed or been cancelled, this method has no effect.
- *
- *
- * @param taskId the unique identifier of the scheduled task to cancel
- */
- void cancelTask(int taskId);
-
- /**
- * Shuts down the scheduler and cancels all pending asynchronous tasks.
- *
- * This method should be called during plugin disable or application shutdown
- * to ensure that no background tasks continue running after the application stops.
- * After calling this method, the scheduler should reject any new tasks.
- *
- *
- * @implNote Implementations should ensure proper termination of all internal executor services
- * or scheduling mechanisms to prevent resource leaks or thread hangs.
- */
- void shutdown();
-}
diff --git a/src/main/java/com/github/imdmk/automessage/util/DurationUtil.java b/src/main/java/com/github/imdmk/automessage/util/DurationUtil.java
deleted file mode 100644
index 8be2abe..0000000
--- a/src/main/java/com/github/imdmk/automessage/util/DurationUtil.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package com.github.imdmk.automessage.util;
-
-import dev.rollczi.litecommands.time.DurationParser;
-import dev.rollczi.litecommands.time.TemporalAmountParser;
-import org.jetbrains.annotations.NotNull;
-
-import java.time.Duration;
-import java.time.temporal.ChronoUnit;
-
-/**
- * Utility class for parsing and formatting {@link Duration} values.
- */
-public final class DurationUtil {
-
- private static final TemporalAmountParser DATE_TIME_PARSER = new DurationParser()
- .withUnit("s", ChronoUnit.SECONDS)
- .withUnit("m", ChronoUnit.MINUTES)
- .withUnit("h", ChronoUnit.HOURS)
- .withUnit("d", ChronoUnit.DAYS)
- .withUnit("w", ChronoUnit.WEEKS)
- .withUnit("mo", ChronoUnit.MONTHS)
- .withUnit("y", ChronoUnit.YEARS);
-
- private DurationUtil() {
- throw new UnsupportedOperationException("This is utility class.");
- }
-
- /**
- * Formats a given duration into a human-readable string.
- *
- * @param duration the duration to format
- * @return a formatted string or "<1s" if duration is zero or negative
- */
- @NotNull
- public static String format(@NotNull final Duration duration) {
- if (duration.isZero() || duration.isNegative()) {
- return "<1s";
- }
-
- return DATE_TIME_PARSER.format(duration);
- }
-
- /**
- * Converts a duration to Minecraft ticks (1 tick = 50ms).
- *
- * @param duration the duration to convert
- * @return duration in ticks
- */
- public static long toTicks(@NotNull final Duration duration) {
- if (duration.isZero() || duration.isNegative()) {
- return 0L;
- }
-
- return duration.toMillis() / 50;
- }
-
- /**
- * Converts Minecraft ticks to a {@link Duration}.
- * 1 tick = 50 milliseconds.
- *
- * @param ticks the number of ticks to convert
- * @return a {@link Duration} representing the given number of ticks
- */
- @NotNull
- public static Duration fromTicks(long ticks) {
- if (ticks <= 0) {
- return Duration.ZERO;
- }
-
- return Duration.ofMillis(ticks * 50L);
- }
-
- @NotNull
- public static TemporalAmountParser parser() {
- return DATE_TIME_PARSER;
- }
-}