update checker

This commit is contained in:
alexd 2026-02-27 19:44:30 +01:00
parent d801d85242
commit 49abca2ccf
3 changed files with 234 additions and 2 deletions

View file

@ -13,6 +13,7 @@ Here dependencies are used by custom anvil
- [modrinth-publish](https://github.com/cloudnode-pro/modrinth-publish) by Zefir - [modrinth-publish](https://github.com/cloudnode-pro/modrinth-publish) by Zefir
- [discord-webhook](https://github.com/tsickert/discord-webhook) by tsickert - [discord-webhook](https://github.com/tsickert/discord-webhook) by tsickert
- Thanks [bstats](https://bstats.org/) for keeping me motivated - Thanks [bstats](https://bstats.org/) for keeping me motivated
- [ModrinthUpdateChecker](https://github.com/Clickism/ModrinthUpdateChecker) by Clickism and thanks to the modrinth team
### Compatibility ### Compatibility
Here is to credits all the author of plugins Here is to credits all the author of plugins

View file

@ -0,0 +1,214 @@
/*
* MIT License
*
* Copyright (c) 2025 Clickism
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package xyz.alexcrea.cuanvil.update;
import com.google.gson.*;
import org.jetbrains.annotations.Nullable;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Utility class to check for newer versions of a project hosted on Modrinth.
*/
public class ModrinthUpdateChecker {
private static final String API_URL = "https://api.modrinth.com/v2/project/{id}/version";
private final String projectId;
private final String loader;
@Nullable
private final String minecraftVersion;
@Nullable
private Boolean featured = null;
@Nullable
public Consumer<Exception> onError = null;
@Nullable
public Function<String, String> getRawVersion = ModrinthUpdateChecker::getRawVersion;
/**
* Create a new update checker for the given project.
* This will check the latest version for the given loader and any minecraft version.
*
* @param projectId the project ID
* @param loader the loader
*/
public ModrinthUpdateChecker(String projectId, String loader) {
this(projectId, loader, null);
}
/**
* Create a new update checker for the given project.
* This will check the latest version for the given loader and minecraft version.
*
* @param projectId the project ID
* @param loader the loader
* @param minecraftVersion the minecraft version, or null for any version
*/
public ModrinthUpdateChecker(String projectId, String loader, @Nullable String minecraftVersion) {
this.projectId = projectId;
this.loader = loader;
this.minecraftVersion = minecraftVersion;
}
/**
* Check the latest version of the project for the given loader and minecraft version
* and call the consumer with it.
*
* @param consumer the consumer
*/
public void checkVersion(Consumer<String> consumer) {
try {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(prepareURI())
.GET()
.build();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenAcceptAsync(response -> {
if (response.statusCode() != 200) {
if(onError != null)
onError.accept(new RuntimeException("wrong response status code: " + response.statusCode()));
return;
}
JsonArray versionsArray = JsonParser.parseString(response.body()).getAsJsonArray();
String latestVersion = getLatestVersion(versionsArray);
if (latestVersion == null) {
if(onError != null)
onError.accept(new RuntimeException("latest version is null"));
return;
}
consumer.accept(latestVersion);
});
} catch (Exception e) {
if(onError != null) onError.accept(e);
}
}
/**
* Get the latest compatible version from the versions array.
*
* @param versions the versions array
* @return the latest compatible version
*/
@Nullable
protected String getLatestVersion(JsonArray versions) {
return versions.asList().stream().findFirst()
.map(JsonElement::getAsJsonObject)
.map(version -> version.get("version_number").getAsString())
.map(getRawVersion != null ? getRawVersion : (v -> v))
.orElse(null);
}
/**
* Gets the raw version from a version string.
* i.E: "fabric-1.2+1.17.1" -> "1.2"
*
* @param version the version string
* @return the raw version string
*/
public static String getRawVersion(String version) {
if (version.isEmpty()) return version;
version = version.replaceAll("^\\D+", "");
String[] split = version.split("\\+");
return split[0];
}
/**
* Prepare this request uri based on current parameters.
* @return the request uri
*/
private URI prepareURI() {
var url = new StringBuilder(API_URL.replace("{id}", projectId));
var parameters = prepareParameters();
String[] paramArray = new String[parameters.size()];
int i = 0;
for (Map.Entry<String, String> entry : parameters.entrySet()) {
paramArray[i++] = entry.getKey() + '=' + entry.getValue();
}
url.append('?').append(String.join("&", paramArray));
return URI.create(url.toString());
}
/**
* Get the parameters for the version request.
*
* @return a map of key-value map of the request parameters
*/
private Map<String, String> prepareParameters(){
var parameters = new HashMap<String, String>();
parameters.put("loaders", List.of(loader).toString());
if(minecraftVersion != null) parameters.put("game_versions", List.of(minecraftVersion).toString());
if(featured != null) parameters.put("featured", featured.toString());
parameters.put("include_changelog", "false");
return parameters;
}
/**
* Only get featured or non-featured versions.
* Null represent no filter.
* @param featured should be restricted to featured version ? default null if not called
* @return this
*/
public ModrinthUpdateChecker setFeatured(@Nullable Boolean featured) {
this.featured = featured;
return this;
}
/**
* Function called on error calling the api.
* @param onError What should happen on error
* @return this
*/
public ModrinthUpdateChecker setOnError(@Nullable Consumer<Exception> onError) {
this.onError = onError;
return this;
}
/**
* Set the function to get raw version from the modrinth version.
* If null provided raw version will act as in the identity function.
* @param getRawVersion The function transforming modrinth version to raw version
* @return this
*/
public ModrinthUpdateChecker setGetRawVersion(@Nullable Function<String, String> getRawVersion) {
this.getRawVersion = getRawVersion;
return this;
}
}

View file

@ -18,6 +18,7 @@ import xyz.alexcrea.cuanvil.listener.AnvilCloseListener
import xyz.alexcrea.cuanvil.listener.AnvilResultListener import xyz.alexcrea.cuanvil.listener.AnvilResultListener
import xyz.alexcrea.cuanvil.listener.ChatEventListener import xyz.alexcrea.cuanvil.listener.ChatEventListener
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener
import xyz.alexcrea.cuanvil.update.ModrinthUpdateChecker
import xyz.alexcrea.cuanvil.update.PluginSetDefault import xyz.alexcrea.cuanvil.update.PluginSetDefault
import xyz.alexcrea.cuanvil.update.UpdateHandler import xyz.alexcrea.cuanvil.update.UpdateHandler
import xyz.alexcrea.cuanvil.util.Metrics import xyz.alexcrea.cuanvil.util.Metrics
@ -31,8 +32,9 @@ import java.util.logging.Level
open class CustomAnvil : JavaPlugin() { open class CustomAnvil : JavaPlugin() {
companion object { companion object {
// bstats plugin id // pluginIDS
private const val bstatsPluginId = 20923 private const val bstatsPluginId = 20923
private const val modrinthPluginID = "S75Ueiq9"
// Permission string required to use the plugin's features // Permission string required to use the plugin's features
const val affectedByPluginPermission = "ca.affected" const val affectedByPluginPermission = "ca.affected"
@ -174,10 +176,25 @@ open class CustomAnvil : JavaPlugin() {
logger.warning("Please note CustomAnvil is a more recent version of UnsafeEnchantsPlus") logger.warning("Please note CustomAnvil is a more recent version of UnsafeEnchantsPlus")
} }
if(!PlatformUtil.isPaper) { val isPaper = PlatformUtil.isPaper
if(!isPaper) {
logger.warning("It seems you are using spigot") logger.warning("It seems you are using spigot")
logger.warning("Please take notice that spigot is less supported than paper and derivatives") logger.warning("Please take notice that spigot is less supported than paper and derivatives")
} }
val loader = if(isPaper) "paper" else "spigot"
val version = description.version
val featured = if(version.contains("dev")) null else true
ModrinthUpdateChecker(modrinthPluginID, loader, null)
.setFeatured(featured)
.setOnError { logger.log(Level.WARNING, "error trying to fetch latest update", it) }
.checkVersion { latestver: String? ->
if(latestver == null || version.contains(latestver)) return@checkVersion
logger.warning("An update may be available: $latestver")
}
} }
private fun registerListeners() { private fun registerListeners() {