Skip to content
Merged

v5 #210

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
47f228c
Bumped version to 5.0.0-alpha.1
OakLoaf Sep 15, 2026
6e8daa6
Corrected velocity tests package name
OakLoaf Sep 15, 2026
73740a7
feat: ✨ Switch to Adventure ComponentLogger
Zoriot Aug 16, 2026
cf2bf21
Merge pull request #193 from Zoriot/ComponentLogger
OakLoaf Sep 15, 2026
c695b22
Merge remote-tracking branch 'origin/v5' into v5
OakLoaf Sep 15, 2026
a78c3da
Removed outdated test file
OakLoaf Sep 15, 2026
0a8f554
Add schedule-frequency config option to allow for scheduling update c…
OakLoaf Sep 16, 2026
f400963
Bumped ViaVersion in dev environment
OakLoaf Sep 16, 2026
1116952
Merge pull request #212 from OakLoaf/check-schedule
OakLoaf Sep 16, 2026
d21f7e3
Changed default Modrinth release channel to 'release'
OakLoaf Sep 16, 2026
2814453
Changed BlueMap modrinth source loader to paper
OakLoaf Sep 16, 2026
5a64581
Merge pull request #213 from OakLoaf/modrinth-channel
OakLoaf Sep 16, 2026
26b3657
Merge remote-tracking branch 'origin/v5' into v5
OakLoaf Sep 16, 2026
240b691
Add Discord webhook notifications for plugin downloads (#157)
Zoriot Aug 18, 2026
1e008e9
Refactor Discord webhook implementation for improved lifecycle manage…
Zoriot Sep 17, 2026
4b6a9ce
Merge pull request #196 from Zoriot/webhook
OakLoaf Sep 22, 2026
a07b634
Implemented Notifier to allow for future alternative notification types
OakLoaf Sep 22, 2026
bc75436
Removed outdated code
OakLoaf Sep 22, 2026
dd869d7
Merge pull request #217 from OakLoaf/notifier
OakLoaf Sep 22, 2026
e09270b
Ensured notifiers field cannot be null
OakLoaf Sep 28, 2026
c49151d
Merge branch 'main' into v5
OakLoaf Sep 28, 2026
a631b98
Fixed compilation error
OakLoaf Sep 30, 2026
bd8b759
Created new empty list instead of clearing
OakLoaf Oct 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {
}

group = "org.lushplugins"
version = "4.3.2"
version = "5.0.0-alpha.1"

allprojects {
plugins.apply("java-library")
Expand Down
2 changes: 2 additions & 0 deletions common/api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@ sourceSets {
dependencies {
api("com.google.guava:guava:33.7.1-jre")
api("com.google.code.gson:gson:2.14.0")
api("net.kyori:adventure-text-logger-slf4j:5.2.0")
api("org.slf4j:slf4j-api:2.0.17")
compileOnlyApi("org.jetbrains:annotations:26.1.0")
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import java.util.logging.Level;

public class PluginData {
private final String pluginName;
Expand Down Expand Up @@ -170,7 +169,7 @@ public boolean checkForUpdate() {
latestVersion = fetchedVersion.version();
sourceContext = fetchedVersion.sourceContext();
} catch (InvalidVersionFormatException e) {
UpdaterConstants.LOGGER.severe("Failed to read latest version for '%s': %s".formatted(this.pluginName, e.getMessage()));
UpdaterConstants.LOGGER.error("Failed to read latest version for {}: {}", this.pluginName, e.getMessage());
return false;
}

Expand All @@ -179,7 +178,7 @@ public boolean checkForUpdate() {
VersionComparator comparator = versionComparator().orElse(sourceContext.sourceData().defaultComparator());
versionDifference = comparator.compare(this.currentVersion, latestVersion);
} catch (InvalidVersionFormatException e) {
UpdaterConstants.LOGGER.severe("Failed to compare versions for '%s': %s".formatted(this.pluginName, e.getMessage()));
UpdaterConstants.LOGGER.error("Failed to compare versions for {}: {}", this.pluginName, e.getMessage());
return false;
}

Expand Down Expand Up @@ -209,11 +208,15 @@ public boolean downloadUpdate(Path destinationDir) {
prepareDownloadableRelease()
.downloadTo(destinationDir);
return true;
} catch (IOException | InterruptedException e) {
UpdaterConstants.LOGGER.log(Level.SEVERE, "Failed to download update for plugin '" + this.pluginName + "'.", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
UpdaterConstants.LOGGER.error("Failed to download update for plugin '{}'.", this.pluginName, e);
return false;
} catch (IOException e) {
UpdaterConstants.LOGGER.error("Failed to download update for plugin '{}'.", this.pluginName, e);
return false;
} catch (RuntimeException e) {
UpdaterConstants.LOGGER.log(Level.SEVERE, "Failed to prepare or download update for plugin '" + this.pluginName + "'.", e);
UpdaterConstants.LOGGER.error("Failed to prepare or download update for plugin '{}'.", this.pluginName, e);
return false;
}
}
Expand All @@ -228,9 +231,12 @@ private <T> T attemptOnSources(SourceSupplier<T> supplier) throws FailedAttempts
try {
return supplier.apply(new SourceContext(source, sourceData));
} catch (InvalidHttpResponse e) {
UpdaterConstants.LOGGER.severe(e.getMessage());
} catch (Throwable e) {
UpdaterConstants.LOGGER.log(Level.SEVERE, e.getMessage(), e);
UpdaterConstants.LOGGER.error(e.getMessage());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
UpdaterConstants.LOGGER.error("Thread interrupted while fetching data from source '{}' for plugin '{}'.", sourceData.sourceName(), this.pluginName, e);
} catch (Exception e) {
UpdaterConstants.LOGGER.error("Failed to fetch data from source '{}' for plugin '{}'.", sourceData.sourceName(), this.pluginName, e);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package org.lushplugins.pluginupdater.api.updater;

import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.util.logging.Logger;

public interface PluginInfo {

Expand All @@ -25,5 +25,5 @@ public interface PluginInfo {
/**
* @return The plugin's logger instance
*/
Logger getLogger();
ComponentLogger getComponentLogger();
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,9 @@
package org.lushplugins.pluginupdater.api.util;

import com.google.gson.Gson;

import java.util.logging.*;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;

public class UpdaterConstants {
public static final Logger LOGGER = Logger.getLogger("PluginUpdater");
public static ComponentLogger LOGGER = ComponentLogger.logger("PluginUpdater");
public static final Gson GSON = new Gson();

static {
for (Handler handler : LOGGER.getHandlers()) {
LOGGER.removeHandler(handler);
}

LOGGER.addHandler(new StreamHandler(System.out, new LogFormatter()));
LOGGER.setUseParentHandlers(false);
}

private static class LogFormatter extends Formatter {
private static final String RESET = "\u001B[0m";
private static final String YELLOW = "\u001B[33m";
private static final String RED = "\u001B[31m";

@Override
public String format(LogRecord record) {
String message = "[%s] [%s] %s".formatted(record.getLevel(), LOGGER.getName(), record.getMessage());

if (record.getLevel() == Level.WARNING) {
message = YELLOW + message + RESET;
} else if (record.getLevel() == Level.SEVERE) {
message = RED + message + RESET;
}

return message + System.lineSeparator();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public void downloadTo(Path destinationDir) throws IOException, InterruptedExcep
// Ensures update folder exists
Files.createDirectories(downloadPath.getParent());

UpdaterConstants.LOGGER.info("Saving '" + fileName + "' to '" + downloadPath.toAbsolutePath() + "'");
UpdaterConstants.LOGGER.info("Saving '{}' to '{}'", fileName, downloadPath.toAbsolutePath());
HttpResponse<Path> downloadResponse = client.send(
request,
HttpResponse.BodyHandlers.ofFile(downloadPath)
Expand Down
1 change: 1 addition & 0 deletions common/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,15 @@
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.logging.Level;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

public class UpdaterImpl<T> {
Expand All @@ -43,6 +41,14 @@ public UpdaterImpl(UpdaterPlatform<T> platform, UpdaterPlugin updaterPlugin, Com
config = new ConfigManager(this);
config.reload();

int delay = config.getScheduleFrequencyMins();
if (delay > 0) {
updateHandler.scheduler().scheduleAtFixedRate(() -> {
updateHandler.queueUpdateChecks(config.getPlugins());
updateHandler.queueBroadcastNotification();
}, delay, delay, TimeUnit.MINUTES);
}

Lamp<?> lamp = commandPlatform.prepareLamp()
.permissionFactory(new CommandPermissionFactory(this))
.parameterTypes(parameterTypes -> parameterTypes
Expand Down Expand Up @@ -84,6 +90,7 @@ public UpdaterImpl(UpdaterPlatform<T> platform, UpdaterPlugin updaterPlugin, Com

public void shutdown() {
updateHandler.shutdown();
config.shutdownNotifiers();
}

public UpdaterPlatform<T> platform() {
Expand Down Expand Up @@ -127,8 +134,8 @@ public CompletableFuture<List<PluginData>> collectUnknownPlugins() {
List<PluginData> foundPluginData;
try {
foundPluginData = collector.collect(unknownPlugins.values());
} catch (Throwable e) {
updaterPlugin.getLogger().log(Level.WARNING, "Caught exception whilst collecting unknown plugin data: ", e);
} catch (Exception e) {
updaterPlugin.getComponentLogger().warn("Caught exception whilst collecting unknown plugin data: ", e);
continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import java.io.IOException;
import java.net.http.HttpResponse;
import java.util.*;
import java.util.logging.Level;

public record ModrinthCollector(UpdaterImpl<?> updater) implements PluginDataCollector {

Expand All @@ -34,7 +33,7 @@ public List<PluginData> collect(Collection<PluginInfo> plugins) {
hash = Files.asByteSource(pluginFile).hash(Hashing.sha512());
pluginHashes.put(hash.toString(), unknownPlugin);
} catch (IOException e) {
updater.updaterPlugin().getLogger().log(Level.WARNING, "Caught error whilst hashing plugin file: ", e);
updater.updaterPlugin().getComponentLogger().warn("Caught error whilst hashing plugin file: ", e);
}
}

Expand All @@ -49,13 +48,17 @@ public List<PluginData> collect(Collection<PluginInfo> plugins) {
HttpResponse<String> response;
try {
response = HttpUtil.sendRequest(String.format("%s/version_files", ModrinthSource.ENDPOINT.url()), payload);
} catch (IOException | InterruptedException e) {
updater.updaterPlugin().getLogger().log(Level.WARNING, "Caught error whilst getting project data from hashes: ", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
updater.updaterPlugin().getComponentLogger().warn("Caught error whilst getting project data from hashes: ", e);
return Collections.emptyList();
} catch (IOException e) {
updater.updaterPlugin().getComponentLogger().warn("Caught error whilst getting project data from hashes: ", e);
return Collections.emptyList();
}

if (response.statusCode() != 200) {
updater.updaterPlugin().getLogger().log(Level.WARNING, "Received invalid response code (" + response.statusCode() + ") whilst getting project data from hashes.");
updater.updaterPlugin().getComponentLogger().warn("Received invalid response code ({}) whilst getting project data from hashes.", response.statusCode());
return Collections.emptyList();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import org.lushplugins.pluginupdater.api.updater.PluginData;
import org.lushplugins.pluginupdater.api.updater.PluginInfo;
import org.lushplugins.pluginupdater.common.UpdaterImpl;
import org.lushplugins.pluginupdater.common.command.annotation.CommandPermission;
import org.lushplugins.pluginupdater.common.command.annotation.PluginName;
import org.lushplugins.pluginupdater.common.UpdaterImpl;
import org.lushplugins.pluginupdater.common.updater.UpdateHandler;
import revxrsal.commands.annotation.Subcommand;
import revxrsal.commands.annotation.Switch;
Expand All @@ -13,7 +13,6 @@

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;

@SuppressWarnings("unused")
public record UpdaterCommand(UpdaterImpl<?> updater) implements OrphanCommand {
Expand All @@ -23,8 +22,8 @@ public record UpdaterCommand(UpdaterImpl<?> updater) implements OrphanCommand {
public String reload(@Switch("skip-check") boolean skipCheck) {
try {
updater.config().reload(skipCheck);
} catch (Throwable e) {
updater.updaterPlugin().getLogger().log(Level.SEVERE, "Caught error whilst reloading: ", e);
} catch (Exception e) {
updater.updaterPlugin().getComponentLogger().error("Caught error whilst reloading: ", e);
return "<#ff6969>Something went wrong whilst reloading the plugin, check the console for errors";
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,27 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lushplugins.pluginupdater.api.updater.PluginData;
import org.lushplugins.pluginupdater.common.config.deserializer.PluginDataDeserializer;
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;

import java.io.IOException;
import java.nio.file.Files;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Collectors;

public class ConfigManager {
private final UpdaterImpl<?> updater;
private boolean allowDownloads;
private int scheduleFrequencyMins;
private final Map<String, PluginData> plugins = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private final Set<String> disabledPlugins = new HashSet<>();
private Messages messages;
private String discordWebhookUrl;
private List<Notifier> notifiers = Collections.emptyList();

public ConfigManager(UpdaterImpl<?> updater) {
this.updater = updater;
Expand All @@ -42,10 +46,23 @@ public void reload(boolean skipCheck) {

boolean checkOnReload = ConfigUtil.getOrAliasOrElse(
config, "check-updates-on-reload", "check-updates-on-start", true,
() -> updater.updaterPlugin().getLogger().log(Level.WARNING, "Deprecated: The config section 'check-updates-on-start' has been renamed to 'check-updates-on-reload'")
() -> updater.updaterPlugin().getComponentLogger().warn("Deprecated: The config section 'check-updates-on-start' has been renamed to 'check-updates-on-reload'")
);

this.allowDownloads = config.getOrElse("allow-downloads", true);
this.scheduleFrequencyMins = config.getOrElse("schedule-frequency", -1);

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) {
Expand Down Expand Up @@ -86,11 +103,18 @@ public void reload(boolean skipCheck) {

if (checkOnReload && !skipCheck) {
UpdateHandler<?> updateHandler = updater.updateHandler();
getPlugins().forEach(updateHandler::queueUpdateCheck);
updateHandler.queueUpdateChecks(getPlugins());
updateHandler.queueBroadcastNotification();
}
});

this.notifiers.forEach(Notifier::shutdown);
Comment thread
OakLoaf marked this conversation as resolved.
if (config.contains("notifications")) {
this.notifiers = NotifierRegistry.deserializeNotifiers(updater, config.get("notifications"));
} else {
this.notifiers = Collections.emptyList();
}

config.close();
}

Expand All @@ -102,6 +126,10 @@ public boolean shouldAllowDownloads() {
return allowDownloads;
}

public int getScheduleFrequencyMins() {
return scheduleFrequencyMins;
}

public boolean canRegisterPluginData(String pluginName) {
return !plugins.containsKey(pluginName) && !disabledPlugins.contains(pluginName);
}
Expand Down Expand Up @@ -142,6 +170,19 @@ public String getMessage(String name, String def) {
return messages.get(name, def);
}

public String getDiscordWebHookUrl() {
return discordWebhookUrl;
}

public List<Notifier> getNotifiers() {
return notifiers;
}

public void shutdownNotifiers() {
Comment thread
OakLoaf marked this conversation as resolved.
notifiers.forEach(Notifier::shutdown);
notifiers = Collections.emptyList();
}


public record Messages(Map<String, String> messages) {

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading
Loading