diff --git a/common/impl/build.gradle.kts b/common/impl/build.gradle.kts index 4e7af67f..82f42641 100644 --- a/common/impl/build.gradle.kts +++ b/common/impl/build.gradle.kts @@ -3,4 +3,5 @@ dependencies { api("io.github.revxrsal:lamp.common:4.0.0-rc.18") api("com.electronwill.night-config:yaml:3.9.0") + implementation("io.github.4drian3d:jdwebhooks:2.0.0") } diff --git a/common/impl/src/main/java/org/lushplugins/pluginupdater/common/UpdaterImpl.java b/common/impl/src/main/java/org/lushplugins/pluginupdater/common/UpdaterImpl.java index be27cc81..6f6a6829 100644 --- a/common/impl/src/main/java/org/lushplugins/pluginupdater/common/UpdaterImpl.java +++ b/common/impl/src/main/java/org/lushplugins/pluginupdater/common/UpdaterImpl.java @@ -10,15 +10,13 @@ import org.lushplugins.pluginupdater.common.command.annotation.PluginName; import org.lushplugins.pluginupdater.common.command.response.StringMessageResponseHandler; import org.lushplugins.pluginupdater.common.config.ConfigManager; +import org.lushplugins.pluginupdater.common.notifier.DiscordWebHookNotifier; import org.lushplugins.pluginupdater.common.platform.CommandHandler; import org.lushplugins.pluginupdater.common.platform.UpdaterPlugin; import org.lushplugins.pluginupdater.common.updater.UpdateHandler; import revxrsal.commands.Lamp; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -83,6 +81,7 @@ public UpdaterImpl(UpdaterPlatform platform, UpdaterPlugin updaterPlugin, Com public void shutdown() { updateHandler.shutdown(); + config.shutdownNotifiers(); } public UpdaterPlatform platform() { diff --git a/common/impl/src/main/java/org/lushplugins/pluginupdater/common/config/ConfigManager.java b/common/impl/src/main/java/org/lushplugins/pluginupdater/common/config/ConfigManager.java index ca737c39..decc0f48 100644 --- a/common/impl/src/main/java/org/lushplugins/pluginupdater/common/config/ConfigManager.java +++ b/common/impl/src/main/java/org/lushplugins/pluginupdater/common/config/ConfigManager.java @@ -8,6 +8,8 @@ import org.lushplugins.pluginupdater.api.updater.PluginData; import org.lushplugins.pluginupdater.common.UpdaterImpl; import org.lushplugins.pluginupdater.common.config.deserializer.PluginDataDeserializer; +import org.lushplugins.pluginupdater.common.notifier.Notifier; +import org.lushplugins.pluginupdater.common.notifier.NotifierRegistry; import org.lushplugins.pluginupdater.common.updater.UpdateHandler; import org.lushplugins.pluginupdater.common.util.ConfigUtil; @@ -22,6 +24,8 @@ public class ConfigManager { private final Map plugins = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); private final Set disabledPlugins = new HashSet<>(); private Messages messages; + private String discordWebhookUrl; + private List notifiers; public ConfigManager(UpdaterImpl updater) { this.updater = updater; @@ -46,6 +50,18 @@ public void reload(boolean skipCheck) { this.allowDownloads = config.getOrElse("allow-downloads", true); + Config discordConfig = config.get("discord-webhook"); + if (discordConfig != null) { + boolean discordWebhookEnabled = discordConfig.getOrElse("enabled", false); + if (!discordWebhookEnabled) { + discordWebhookUrl = ""; + } else { + this.discordWebhookUrl = discordConfig.getOrElse("webhook-url", ""); + } + } else { + this.discordWebhookUrl = ""; + } + Config messagesConfig = config.get("messages"); if (messagesConfig != null) { this.messages = new Messages(messagesConfig); @@ -90,6 +106,13 @@ public void reload(boolean skipCheck) { } }); + this.notifiers.forEach(Notifier::shutdown); + if (config.contains("notifications")) { + this.notifiers = NotifierRegistry.deserializeNotifiers(updater, config.get("notifications")); + } else { + this.notifiers = Collections.emptyList(); + } + config.close(); } @@ -141,6 +164,19 @@ public String getMessage(String name, String def) { return messages.get(name, def); } + public String getDiscordWebHookUrl() { + return discordWebhookUrl; + } + + public List getNotifiers() { + return notifiers; + } + + public void shutdownNotifiers() { + notifiers.forEach(Notifier::shutdown); + notifiers.clear(); + } + public record Messages(Map messages) { diff --git a/common/impl/src/main/java/org/lushplugins/pluginupdater/common/config/deserializer/NotifierDeserializer.java b/common/impl/src/main/java/org/lushplugins/pluginupdater/common/config/deserializer/NotifierDeserializer.java new file mode 100644 index 00000000..2c83e4b8 --- /dev/null +++ b/common/impl/src/main/java/org/lushplugins/pluginupdater/common/config/deserializer/NotifierDeserializer.java @@ -0,0 +1,15 @@ +package org.lushplugins.pluginupdater.common.config.deserializer; + +import com.electronwill.nightconfig.core.Config; +import org.jetbrains.annotations.Nullable; +import org.lushplugins.pluginupdater.common.UpdaterImpl; +import org.lushplugins.pluginupdater.common.notifier.DiscordWebHookNotifier; +import org.lushplugins.pluginupdater.common.notifier.Notifier; + +public class NotifierDeserializer { + + public static @Nullable Notifier discordWebHookNotifier(UpdaterImpl updater, Config config) { + String webHookUrl = config.get("webhook-url"); + return !webHookUrl.isBlank() ? new DiscordWebHookNotifier(updater.updaterPlugin().getComponentLogger(), webHookUrl) : null; + } +} diff --git a/common/impl/src/main/java/org/lushplugins/pluginupdater/common/notifier/DiscordWebHookNotifier.java b/common/impl/src/main/java/org/lushplugins/pluginupdater/common/notifier/DiscordWebHookNotifier.java new file mode 100644 index 00000000..20665688 --- /dev/null +++ b/common/impl/src/main/java/org/lushplugins/pluginupdater/common/notifier/DiscordWebHookNotifier.java @@ -0,0 +1,84 @@ +package org.lushplugins.pluginupdater.common.notifier; + +import io.github._4drian3d.jdwebhooks.component.Component; +import io.github._4drian3d.jdwebhooks.component.ContainerableComponent; +import io.github._4drian3d.jdwebhooks.webhook.WebHookClient; +import io.github._4drian3d.jdwebhooks.webhook.WebHookExecution; +import net.kyori.adventure.text.logger.slf4j.ComponentLogger; +import org.jetbrains.annotations.NotNull; +import org.lushplugins.pluginupdater.api.updater.PluginData; +import org.lushplugins.pluginupdater.api.version.Version; +import org.lushplugins.pluginupdater.api.version.VersionDifference; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class DiscordWebHookNotifier implements Notifier { + private final ComponentLogger logger; + private final WebHookClient webHookClient; + + public DiscordWebHookNotifier(ComponentLogger logger, @NotNull String webhookUrl) { + this.logger = logger; + + try { + this.webHookClient = WebHookClient.fromURL(webhookUrl); + } catch (Exception e) { + logger.warn("Failed to create Discord webhook client. Webhook notifications will be disabled. Possible wrong url.", e); + throw e; + } + } + + @Override + public void notifyDownload(PluginData pluginData) { + try { + Version currentVersion = pluginData.currentVersion(); + Optional latestVersionOptional = pluginData.latestVersion(); + if (latestVersionOptional.isEmpty()) { + return; + } + + String pluginName = pluginData.pluginName(); + + String versionString = "%s → %s".formatted( + currentVersion.rawVersionString(), + latestVersionOptional.get().rawVersionString()); + + List components = new ArrayList<>(); + components.add(Component.textDisplay("**" + pluginName + " Updated**")); + components.add(Component.textDisplay("**Version:** " + versionString)); + + VersionDifference versionDiff = pluginData.versionDifference(); + if (versionDiff != VersionDifference.UNKNOWN) { + components.add(Component.textDisplay("**Update Type:** " + versionDiff.name())); + } + + pluginData.getChangelogUrl().ifPresent(s -> components.add(Component.textDisplay("**Changelog:** [View Changelog](" + s + ")"))); + + WebHookExecution webHook = WebHookExecution.builder() + .username("PluginUpdater") + .avatarURL("https://cdn.modrinth.com/data/IBSpJfbm/172c14d2cdb854064160fa627f9dd0043c1b79ee_96.webp") + .component( + Component.container() + .components(components) + .accentColor(0x66b04f) + .build() + ) + .build(); + + webHookClient.executeWebHook(webHook) + .whenComplete((response, throwable) -> { + int statusCode = response.statusCode(); + + if (throwable != null) { + logger.warn("Failed to send Discord webhook notification", throwable); + } else if (statusCode < 200 || statusCode >= 300) { + logger.warn("Discord webhook returned unsuccessful response. Status code: {}, response: {}", statusCode, response); + } + }); + + } catch (Exception e) { + logger.warn("Failed to send Discord webhook notification", e); + } + } +} diff --git a/common/impl/src/main/java/org/lushplugins/pluginupdater/common/notifier/Notifier.java b/common/impl/src/main/java/org/lushplugins/pluginupdater/common/notifier/Notifier.java new file mode 100644 index 00000000..5bf405c0 --- /dev/null +++ b/common/impl/src/main/java/org/lushplugins/pluginupdater/common/notifier/Notifier.java @@ -0,0 +1,10 @@ +package org.lushplugins.pluginupdater.common.notifier; + +import org.lushplugins.pluginupdater.api.updater.PluginData; + +public interface Notifier { + + void notifyDownload(PluginData pluginData); + + default void shutdown() {} +} diff --git a/common/impl/src/main/java/org/lushplugins/pluginupdater/common/notifier/NotifierRegistry.java b/common/impl/src/main/java/org/lushplugins/pluginupdater/common/notifier/NotifierRegistry.java new file mode 100644 index 00000000..859669e6 --- /dev/null +++ b/common/impl/src/main/java/org/lushplugins/pluginupdater/common/notifier/NotifierRegistry.java @@ -0,0 +1,65 @@ +package org.lushplugins.pluginupdater.common.notifier; + +import com.electronwill.nightconfig.core.Config; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.lushplugins.pluginupdater.common.UpdaterImpl; +import org.lushplugins.pluginupdater.common.config.deserializer.NotifierDeserializer; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiFunction; + +public class NotifierRegistry { + private static final Map, Config, Notifier>> notifiers = new HashMap<>(); + + static { + register("discord-webhook", NotifierDeserializer::discordWebHookNotifier); + } + + public static @Nullable Notifier deserializeNotifier(UpdaterImpl updater, String id, Config config) { + if (config.get("enabled") == Boolean.FALSE) { + return null; + } + + try { + return notifiers.containsKey(id) ? notifiers.get(id).apply(updater, config) : null; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public static List deserializeNotifiers(UpdaterImpl updater, Config config) { + return config.entrySet().stream() + .map(section -> { + if (section.getValue() instanceof Config sectionConfig) { + String key = section.getKey(); + if (notifiers.containsKey(key)) { + return deserializeNotifier(updater, key, sectionConfig); + } else { + String type = sectionConfig.get("type"); + return deserializeNotifier(updater, type, sectionConfig); + } + } + + return null; + }) + .filter(Objects::nonNull) + .toList(); + } + + public static void register(@NotNull String id, BiFunction, Config, Notifier> constructor) { + if (notifiers.containsKey(id)) { + throw new IllegalStateException("This notifier is already registered"); + } + + notifiers.put(id, constructor); + } + + public static void unregister(String id) { + notifiers.remove(id); + } +} diff --git a/common/impl/src/main/java/org/lushplugins/pluginupdater/common/updater/UpdateHandler.java b/common/impl/src/main/java/org/lushplugins/pluginupdater/common/updater/UpdateHandler.java index 875665b6..093214bd 100644 --- a/common/impl/src/main/java/org/lushplugins/pluginupdater/common/updater/UpdateHandler.java +++ b/common/impl/src/main/java/org/lushplugins/pluginupdater/common/updater/UpdateHandler.java @@ -121,6 +121,9 @@ private void processQueue() { if (pluginData.downloadUpdate(updater.updaterPlugin().getDownloadDir())) { pluginData.versionDifference(VersionDifference.UNKNOWN); pluginData.setAlreadyDownloaded(true); + + updater.config().getNotifiers().forEach(notifier -> notifier.notifyDownload(pluginData)); + processingData.getFuture().complete(true); break; } else { diff --git a/common/impl/src/main/resources/config.yml b/common/impl/src/main/resources/config.yml index 17571d3f..ec0dab65 100644 --- a/common/impl/src/main/resources/config.yml +++ b/common/impl/src/main/resources/config.yml @@ -12,6 +12,12 @@ plugins: ExamplePlugin: enabled: false +# Notifications for downloads +notifications: + discord-webhook: + enabled: false + webhook-url: "" + messages: unchecked-color: "" update-available-color: "<#ffda54>"