From 64cb0c989570670e6aa1b80db0dfab9af1b845bd Mon Sep 17 00:00:00 2001 From: Geoff Bourne Date: Sat, 2 Aug 2025 15:19:04 -0500 Subject: [PATCH] modrinth: add version-from-modrinth-projects subcommand --- README.md | 34 + .../java/me/itzg/helpers/McImageHelper.java | 4 +- .../VersionFromModrinthProjectsCommand.java | 116 +++ .../itzg/helpers/modrinth/model/Project.java | 9 +- .../helpers/modrinth/model/package-info.java | 4 + ...ersionFromModrinthProjectsCommandTest.java | 111 +++ .../__files/modrinth/project-discordsrv.json | 143 +++ .../modrinth/project-griefprevention.json | 92 ++ .../modrinth/project-viabackwards.json | 554 +++++++++++ .../__files/modrinth/project-viaversion.json | 899 ++++++++++++++++++ 10 files changed, 1962 insertions(+), 4 deletions(-) create mode 100644 src/main/java/me/itzg/helpers/modrinth/VersionFromModrinthProjectsCommand.java create mode 100644 src/main/java/me/itzg/helpers/modrinth/model/package-info.java create mode 100644 src/test/java/me/itzg/helpers/modrinth/VersionFromModrinthProjectsCommandTest.java create mode 100644 src/test/resources/__files/modrinth/project-discordsrv.json create mode 100644 src/test/resources/__files/modrinth/project-griefprevention.json create mode 100644 src/test/resources/__files/modrinth/project-viabackwards.json create mode 100644 src/test/resources/__files/modrinth/project-viaversion.json diff --git a/README.md b/README.md index 5014379e..ea455ca3 100644 --- a/README.md +++ b/README.md @@ -1100,6 +1100,40 @@ share code or pack file ``` +### version-from-modrinth-projects + +``` +Usage: mc-image-helper version-from-modrinth-projects + [--api-base-url=] [--projects=[loader:]id|slug[:version][,| + [loader:]id|slug[:version]...]...]... + [[--connection-pool-pending-acquire-timeout=DURATION] + [--tls-handshake-timeout=DURATION] + [--connection-pool-max-idle-timeout=DURATION] + [--http-response-timeout=DURATION]] +Finds a compatible Minecraft version across given Modrinth projects + --api-base-url= + Default: https://api.modrinth.com + --connection-pool-max-idle-timeout=DURATION + + --connection-pool-pending-acquire-timeout=DURATION + + --http-response-timeout=DURATION + The response timeout to apply to HTTP operations. Parsed from ISO-8601 + format. Default: PT30S + --projects=[loader:]id|slug[:version][,|[loader:]id|slug[: + version]...]... + Project ID or Slug. Can be |, :|, :|:, '@' + Examples: fabric-api, fabric:fabric-api, fabric:fabric-api:0.76.1+1. + 19.2, datapack:terralith, @/path/to/modrinth-mods.txt + Valid release types: release, beta, alpha + Valid loaders: fabric, forge, paper, datapack, etc. + --tls-handshake-timeout=DURATION + Default: PT30S +``` + ### yaml-path ``` diff --git a/src/main/java/me/itzg/helpers/McImageHelper.java b/src/main/java/me/itzg/helpers/McImageHelper.java index ebb5357b..30d39d53 100644 --- a/src/main/java/me/itzg/helpers/McImageHelper.java +++ b/src/main/java/me/itzg/helpers/McImageHelper.java @@ -29,6 +29,7 @@ import me.itzg.helpers.github.GithubCommands; import me.itzg.helpers.modrinth.InstallModrinthModpackCommand; import me.itzg.helpers.modrinth.ModrinthCommand; +import me.itzg.helpers.modrinth.VersionFromModrinthProjectsCommand; import me.itzg.helpers.mvn.MavenDownloadCommand; import me.itzg.helpers.paper.InstallPaperCommand; import me.itzg.helpers.patch.PatchCommand; @@ -93,8 +94,9 @@ SyncAndInterpolate.class, TestLoggingCommand.class, TomlPathCommand.class, - YamlPathCommand.class, VanillaTweaksCommand.class, + VersionFromModrinthProjectsCommand.class, + YamlPathCommand.class } ) @Slf4j diff --git a/src/main/java/me/itzg/helpers/modrinth/VersionFromModrinthProjectsCommand.java b/src/main/java/me/itzg/helpers/modrinth/VersionFromModrinthProjectsCommand.java new file mode 100644 index 00000000..53e8aa08 --- /dev/null +++ b/src/main/java/me/itzg/helpers/modrinth/VersionFromModrinthProjectsCommand.java @@ -0,0 +1,116 @@ +package me.itzg.helpers.modrinth; + +import static me.itzg.helpers.McImageHelper.SPLIT_COMMA_NL; +import static me.itzg.helpers.McImageHelper.SPLIT_SYNOPSIS_COMMA_NL; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import me.itzg.helpers.errors.GenericException; +import me.itzg.helpers.http.SharedFetchArgs; +import me.itzg.helpers.modrinth.model.Project; +import picocli.CommandLine.ArgGroup; +import picocli.CommandLine.Command; +import picocli.CommandLine.ExitCode; +import picocli.CommandLine.Option; +import reactor.core.publisher.Flux; + +@Command(name = "version-from-modrinth-projects", description = "Finds a compatible Minecraft version across given Modrinth projects") +public class VersionFromModrinthProjectsCommand implements Callable { + + @Option( + names = "--projects", + description = "Project ID or Slug. Can be |," + + " :|," + + " :|:," + + " '@'" + + "%nExamples: fabric-api, fabric:fabric-api, fabric:fabric-api:0.76.1+1.19.2," + + " datapack:terralith, @/path/to/modrinth-mods.txt" + + "%nValid release types: release, beta, alpha" + + "%nValid loaders: fabric, forge, paper, datapack, etc.", + split = SPLIT_COMMA_NL, + splitSynopsisLabel = SPLIT_SYNOPSIS_COMMA_NL, + paramLabel = "[loader:]id|slug[:version]", + // at least one is required + arity = "1..*" + ) + List projects; + + @Option(names = "--api-base-url", defaultValue = "${env:MODRINTH_API_BASE_URL:-https://api.modrinth.com}", + description = "Default: ${DEFAULT-VALUE}" + ) + String baseUrl; + + @ArgGroup(exclusive = false) + SharedFetchArgs sharedFetchArgs = new SharedFetchArgs(); + + @Override + public Integer call() throws Exception { + try (ModrinthApiClient modrinthApiClient = new ModrinthApiClient(baseUrl, "modrinth", sharedFetchArgs.options())) { + final String version = versionFromProjects(modrinthApiClient, projects); + + if (version != null) { + System.out.println(version); + return ExitCode.OK; + } + else { + System.err.println("Unable to find a compatible Minecraft version across given projects"); + return ExitCode.SOFTWARE; + } + } + } + + static String versionFromProjects(ModrinthApiClient modrinthApiClient, List projectRefs) { + final List> allGameVersions = Flux.fromStream( + // extract just the id/slug from refs + projectRefs.stream() + .map(ProjectRef::parse) + .map(ProjectRef::getIdOrSlug) + ) + .flatMap(modrinthApiClient::getProject) + .map(Project::getGameVersions) + .collectList() + .block(); + + if (allGameVersions != null) { + return processGameVersions(allGameVersions); + } + else { + throw new GenericException("Unable to retrieve game versions for projects " + projectRefs); + } + } + + static String processGameVersions(List> allGameVersions) { + final Map gameVersionCounts = new HashMap<>(); + + final int projectCount = allGameVersions.size(); + + final int[] positions = new int[projectCount]; + for (int i = 0; i < projectCount; i++) { + positions[i] = allGameVersions.get(i).size(); + } + + while (!finished(positions)) { + for (int i = 0; i < projectCount; i++) { + final String version = allGameVersions.get(i).get(--positions[i]); + final Integer result = gameVersionCounts.compute(version, (k, count) -> count == null ? 1 : count + 1); + if (result == projectCount) { + return version; + } + } + } + + return null; + } + + static private boolean finished(int[] positions) { + for (final int position : positions) { + // since we pre-increment the positions + if (position <= 0) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/me/itzg/helpers/modrinth/model/Project.java b/src/main/java/me/itzg/helpers/modrinth/model/Project.java index b7d80c90..7cb6167b 100644 --- a/src/main/java/me/itzg/helpers/modrinth/model/Project.java +++ b/src/main/java/me/itzg/helpers/modrinth/model/Project.java @@ -2,12 +2,11 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; -import lombok.Data; - import java.util.List; +import lombok.Data; /** - * Spec + * Spec */ @Data @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) @@ -23,4 +22,8 @@ public class Project { ServerSide serverSide; List versions; + + List gameVersions; + + List loaders; } diff --git a/src/main/java/me/itzg/helpers/modrinth/model/package-info.java b/src/main/java/me/itzg/helpers/modrinth/model/package-info.java new file mode 100644 index 00000000..3ada95ae --- /dev/null +++ b/src/main/java/me/itzg/helpers/modrinth/model/package-info.java @@ -0,0 +1,4 @@ +/** + * Model classes supporting Modrinth Labrinth API + */ +package me.itzg.helpers.modrinth.model; \ No newline at end of file diff --git a/src/test/java/me/itzg/helpers/modrinth/VersionFromModrinthProjectsCommandTest.java b/src/test/java/me/itzg/helpers/modrinth/VersionFromModrinthProjectsCommandTest.java new file mode 100644 index 00000000..3e3f73e1 --- /dev/null +++ b/src/test/java/me/itzg/helpers/modrinth/VersionFromModrinthProjectsCommandTest.java @@ -0,0 +1,111 @@ +package me.itzg.helpers.modrinth; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.argumentSet; + +import com.github.stefanbirkner.systemlambda.SystemLambda; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.FieldSource; +import picocli.CommandLine; +import picocli.CommandLine.ExitCode; + +@WireMockTest +class VersionFromModrinthProjectsCommandTest { + + @ParameterizedTest + @FieldSource("processGameVersionsArgs") + void processGameVersions(List> versions, String expected) { + final String result = VersionFromModrinthProjectsCommand.processGameVersions(versions); + + if (expected != null) { + assertThat(result) + .isNotNull() + .isEqualTo(expected); + } + else { + assertThat(result) + .isNull(); + } + } + + @SuppressWarnings("unused") // will be fixed https://youtrack.jetbrains.com/issue/IDEA-358214/Support-JUnit-5-FieldSource-annotation + static List processGameVersionsArgs = Arrays.asList( + argumentSet("matches", Arrays.asList( + Arrays.asList("1.21.6", "1.21.7", "1.21.8"), + Arrays.asList("1.21.6", "1.21.7", "1.21.8"), + Arrays.asList("1.21.6", "1.21.7", "1.21.8"), + Arrays.asList("1.21.6", "1.21.7", "1.21.8") + ), "1.21.8" + ), + argumentSet("justOneOff", Arrays.asList( + Arrays.asList("1.21.6", "1.21.7", "1.21.8"), + Arrays.asList("1.21.6", "1.21.7", "1.21.8"), + Arrays.asList("1.21.6", "1.21.7"), + Arrays.asList("1.21.6", "1.21.7", "1.21.8") + ), "1.21.7" + ), + argumentSet("mismatch", Arrays.asList( + Arrays.asList("1.21.6", "1.21.7", "1.21.8"), + Arrays.asList("1.21.6", "1.21.7", "1.21.8"), + Arrays.asList("1.21.4", "1.21.5"), + Arrays.asList("1.21.6", "1.21.7", "1.21.8") + ), null + ) + ); + + @Test + void testCommand(WireMockRuntimeInfo wmInfo) throws Exception { + + stubGetProjects("viaversion", "viabackwards", "griefprevention", "discordsrv"); + + final String out = SystemLambda.tapSystemOut(() -> { + final int exitCode = new CommandLine(new VersionFromModrinthProjectsCommand()) + .execute( + "--api-base-url", wmInfo.getHttpBaseUrl(), + "--projects", "viaversion,viabackwards,griefprevention,discordsrv" + ); + + assertThat(exitCode) + .isEqualTo(ExitCode.OK); + }); + + assertThat(out).isEqualToNormalizingNewlines("1.21.7\n"); + } + + @Test + void testCommandWithProjectQualifiers(WireMockRuntimeInfo wmInfo) throws Exception { + + stubGetProjects("viaversion", "viabackwards", "griefprevention", "discordsrv"); + + final String out = SystemLambda.tapSystemOut(() -> { + final int exitCode = new CommandLine(new VersionFromModrinthProjectsCommand()) + .execute( + "--api-base-url", wmInfo.getHttpBaseUrl(), + "--projects", "paper:viaversion,viabackwards,griefprevention:ue7jAjJ5,discordsrv" + ); + + assertThat(exitCode) + .isEqualTo(ExitCode.OK); + }); + + assertThat(out).isEqualToNormalizingNewlines("1.21.7\n"); + } + + private void stubGetProjects(String... projects) { + for (final String project : projects) { + stubFor(get(urlPathEqualTo("/v2/project/" + project)) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json") + .withBodyFile("modrinth/project-" + project + ".json") + ) + ); + } + } +} \ No newline at end of file diff --git a/src/test/resources/__files/modrinth/project-discordsrv.json b/src/test/resources/__files/modrinth/project-discordsrv.json new file mode 100644 index 00000000..2fe8ec30 --- /dev/null +++ b/src/test/resources/__files/modrinth/project-discordsrv.json @@ -0,0 +1,143 @@ +{ + "client_side": "unsupported", + "server_side": "required", + "game_versions": [ + "1.7.10", + "1.8", + "1.8.1", + "1.8.2", + "1.8.3", + "1.8.4", + "1.8.5", + "1.8.6", + "1.8.7", + "1.8.8", + "1.8.9", + "1.9", + "1.9.1", + "1.9.2", + "1.9.3", + "1.9.4", + "1.10", + "1.10.1", + "1.10.2", + "1.11", + "1.11.1", + "1.11.2", + "1.12", + "1.12.1", + "1.12.2", + "1.13", + "1.13.1", + "1.13.2", + "1.14", + "1.14.1", + "1.14.2", + "1.14.3", + "1.14.4", + "1.15", + "1.15.1", + "1.15.2", + "1.16", + "1.16.1", + "1.16.2", + "1.16.3", + "1.16.4", + "1.16.5", + "1.17", + "1.17.1", + "1.18", + "1.18.1", + "1.18.2", + "1.19", + "1.19.1", + "1.19.2", + "1.19.3", + "1.19.4", + "1.20", + "1.20.1", + "1.20.2", + "1.20.3", + "1.20.4", + "1.20.5", + "1.20.6", + "1.21", + "1.21.1", + "1.21.2", + "1.21.3", + "1.21.4", + "1.21.5", + "1.21.6", + "1.21.7", + "1.21.8" + ], + "id": "UmLGoGij", + "slug": "discordsrv", + "project_type": "mod", + "team": "ZgPb4r1S", + "organization": null, + "title": "DiscordSRV", + "description": "The most powerful, configurable, open-source Discord to Minecraft bridging plugin available.", + "body": "![DiscordSRV](https://lol.scarsz.me/AiKvTS/Logo-filled-stroke.png) \n\n**The most powerful, configurable, open-source Discord bridge plugin out there.** \n\n**Supports All Minecraft Versions from 1.7.9 to 1.21.x** \n\n_**We only give support for the latest release/dev builds; please update before asking for help**_\n\n## Features\n- Bridge between Minecraft and Discord chats\n- Forward your Minecraft Console to a Discord text channel and execute commands from Discord\n- Broadcast alerts based on certain events ([`alerts.yml`](alerts))\n- Voice Proximity through the Discord Voice Chat ([`voice.yml`](voice))\n- Require linking accounts (or certain role/s) to play ([`linking.yml`](linking))\n- Link your Minecraft account to your discord account ([`config.yml`](config) & [`synchronization.yml`](synchronization))\n - Minecraft group <-> Discord role sync\n - Minecraft (display) name -> Discord (nick) name sync\n - Minecraft <-> Discord ban sync\n - Rewarding players (for being in the Discord server / boosting etc)\n - Knowing who's who (with /discord linked, verification)\n - Administrative purposes\n- Supports many languages\n- Chinese, Danish, Dutch, English, Estonian, French, German, Japanese, Korean, Polish, Russian, Spanish, and Ukrainian\n- Support for popular chat plugins (listed below)\n- Highly customizable\n\n### Plugins we hook into:\n* #### Chat\n * [Herochat], [LegendChat], [LunaChat], [TownyChat], [VentureChat]\n* #### Vanish\n * [Essentials], [PhantomAdmin], [SuperVanish], [VanishNoPacket]\n* #### World\n * [Multiverse]\n* [Vault]\n* [LuckPerms]\n* [PlaceholderAPI] - [DiscordSRV Expansion Placeholders](papi-placeholders)\n\n### Plugins that use our API:\n* [DiscordSRV StaffChat]\n* [ChatControlRed]\n* [CMI]\n* [Plan]\n* [EmojiChat]\n* [PurpleIRC]\n* [ChatReplay]\n* [AuctionHouse]\n* [Staff Facilities]\n* [Staff++]\n* [LiteBansBridge]\n* [MCSF (My Christian Swear Filter)]\n* [MZP-VoteParty]\n* [InteractiveChat DiscordSRV Addon] (Not associated with DiscordSRV)\n* [DiscordSRVUtils] (Not associated with DiscordSRV)\n* [ActivityRoles]\n* [EconomyShopGUI]\n* [DiscordSchematicUploader] (Not associated with DiscordSRV)\n* [KiaiMC]\n* [SlashAdditions]\n* [Custom Bans Plus]\n* [DiscordSRVOAuth] (Not associated with DiscordSRV)\n* [Villager Announcer]\n* [DiscordNickSync]\n\n## Intended usage\nBy using this plugin, you are able to give players the ability to chat in-game with players on your Discord server, as well as having people on the Discord server be able to chat with people in the minecraft server - This can be useful for players that still want to communicate with players in-game, but can't access the Minecraft server for whatever reason.\n\nThis plugin has a remote console feature. You can designate a text channel for the plugin to forward console messages, which also runs all messages sent into that channel as commands by the server console (You should restrict sending this channel to a developer or high ranking role only). Due to how Discord's permissions work, you can have some server roles have access to see the console, while also not allowing them to send messages in that channel, thus creating a read-only console for trusted staff members.\n\nBoth chat and console are toggleable through the configuration file. Some options can be refreshed with `/discordsrv reload` by an OP or a player with the `discordsrv.reload` permission.\n\n## Bot Permissions\n\n### Server Permissions\n\n| Permission | Features |\n|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `Manage Roles` | [role synchronization](https://config.discordsrv.com/synchronization/GroupRoleSynchronizationGroupsAndRolesToSync) and [adding roles to linked players](https://config.discordsrv.com/config/MinecraftDiscordAccountLinkedRoleNameToAddUserTo) |\n| `Manage Channels` | [channel topic updater](https://config.discordsrv.com/messages/ChannelTopicUpdaterChatChannelTopicFormat) and the [voice module](https://config.discordsrv.com/voice/_) |\n| `Ban Members` | [ban synchronization](https://config.discordsrv.com/synchronization/BanSynchronizationDiscordToMinecraft) |\n| `Manage Nicknames` | [nickname synchronization](https://config.discordsrv.com/synchronization/NicknameSynchronizationEnabled) |\n| `Manage Webhooks` | [experimental webhook usage](https://config.discordsrv.com/config/Experiment_WebhookChatMessageDelivery) |\n| | (Server-wide permission is _recommended_, but can be applied on a per-channel basis) |\n\n### Channel Permissions\n\n| Permission | Features |\n|---------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `Read Text Channels & See Voice Channels` | Required for DiscordSRV's channel options (including [console](https://config.discordsrv.com/config/DiscordConsoleChannelId) and [voice lobby](https://config.discordsrv.com/voice/Lobby%20channel)), the [voice module category](https://config.discordsrv.com/voice/Voice%20category) and any other channels you want for [canned responses](https://config.discordsrv.com/config/DiscordCannedResponses) |\n| and `Send Messages` | |\n| `Manage Messages` | when messages are deleted by [playerlist](https://config.discordsrv.com/config/DiscordChatChannelListCommandEnabled) & [chat channel commands](https://config.discordsrv.com/config/DiscordChatChannelConsoleCommandEnabled) |\n| `Embed Links` | optionally for embedding ingame-posted links and required when embed messages are used [death](https://config.discordsrv.com/messages/MinecraftPlayerDeathMessage), [join/Leave](https://config.discordsrv.com/messages/MinecraftPlayerJoinMessage) |\n| `Mention @everyone, @here and All Roles` | for mentioning @everyone if added to [allowed mentions in config.yml](https://config.discordsrv.com/config/DiscordChatChannelAllowedMentions) and [mentions enabled](https://config.discordsrv.com/config/DiscordChatChannelTranslateMentions) |\n| `Add Reactions` and `Read Message History` | for when the bot reacts with \"💬\" and \"❗\" to notify a [truncated message](https://config.discordsrv.com/config/DiscordChatChannelTruncateLength) is being sent from Discord to Minecraft |\n| `Move Members`, `Mute Members` and `Manage Permissions` | required for the [voice lobby](https://config.discordsrv.com/voice/Lobby%20channel) and [voice category](https://config.discordsrv.com/voice/Voice%20category) |\n\n\n## Installation\nVisit the [Installation](installation) page for clear and in-depth instructions on installing and setting up DiscordSRV.\n\n## Donations\nFirst off, thank you from the bottom of my heart for the pizza. If you would like to donate, go to https://scarsz.me/donate. $10 is the suggested amount but you can donate however much you would like- anything is a massive thank you from me. In the note put your Discord username and if you're in DiscordSRV's server you'll be set as a donator and you'll receive some neat perks in the future. If you donated without the note, send me a PM on Discord and I'll manually check it.\n## Developers\nIf you want to interface DiscordSRV with your plugin, you can do so by adding the Maven dependency or adding the plugin jar (DiscordSRV version 1.18.0+) to your project. You also need to add the JDA repository. For an example of this, see [DiscordSRV-ApiTest](https://github.com/DiscordSRV/DiscordSRV-ApiTest). Be sure to add \"DiscordSRV\" to your plugin's `plugin.yml` depends/softdepends list.\n\n### Maven\n```xml\n\n Scarsz-Nexus\n https://nexus.scarsz.me/content/groups/public/\n \n... \n\n com.discordsrv\n discordsrv\n 1.29.0\n provided\n\n``` \n\n### Gradle\n\n```js\nrepositories {\n maven { url 'https://nexus.scarsz.me/content/groups/public/' }\n} \ndependencies {\n compileOnly 'com.discordsrv:discordsrv:1.29.0'\n}\n```\n\n## Data usage\n### Data collection\nAnything and everything shown at https://bstats.org/plugin/bukkit/DiscordSRV will be visible to the public with your server included in the statistics. This is only for statistics; no private information of your server is sent. If you don't want your server included in this, specify the config option `MetricsDisabled` and set it to `true` in the config.yml file.\n\n## Update checking\nDiscordSRV checks for updates using GitHub's API, and makes sure the version is safe to use via a minimum version (security feature), you may disable update checking by setting `UpdateCheckDisabled` to `true` in the config.yml file; however this may leave your server at risk if there is a security issue/exploit and you're running a vulnerable version.\n\n[Herochat]: https://www.spigotmc.org/resources/34305/\n[LegendChat]: https://www.spigotmc.org/resources/6268/\n[LunaChat]: https://github.com/ucchyocean/LunaChat/\n[TownyChat]: https://www.spigotmc.org/resources/towny-72694/\n[VentureChat]: https://www.spigotmc.org/resources/771/\n[Essentials]: https://www.spigotmc.org/resources/9089/\n[PhantomAdmin]: https://www.spigotmc.org/resources/37845/\n[SuperVanish]: https://www.spigotmc.org/resources/1331/\n[VanishNoPacket]: https://dev.bukkit.org/projects/vanish/\n[Multiverse]: https://dev.bukkit.org/projects/multiverse-core/\n[Vault]: https://www.spigotmc.org/resources/34315/\n[LuckPerms]: https://luckperms.net/\n[PlaceholderAPI]: https://www.spigotmc.org/resources/6245/\n\n[DiscordSRV StaffChat]: https://www.spigotmc.org/resources/44245/\n[ChatControlRed]: https://mineacademy.org/chatcontrol/\n[CMI]: https://www.spigotmc.org/resources/3742/\n[Plan]: https://www.spigotmc.org/resources/32536/\n[EmojiChat]: https://www.spigotmc.org/resources/50955/\n[PurpleIRC]: https://www.spigotmc.org/resources/2836/\n[ChatReplay]: https://www.spigotmc.org/resources/28982/\n[AuctionHouse]: https://www.spigotmc.org/resources/61836/\n[Staff Facilities]: https://www.spigotmc.org/resources/13097/\n[Staff++]: https://www.spigotmc.org/resources/83562/\n[LiteBansBridge]: https://www.spigotmc.org/resources/76326/\n[MCSF (My Christian Swear Filter)]: https://www.spigotmc.org/resources/54115/\n[MZP-VoteParty]: https://www.spigotmc.org/resources/89754/\n[InteractiveChat DiscordSRV Addon]: https://www.spigotmc.org/resources/83917/\n[DiscordSRVUtils]: https://www.spigotmc.org/resources/85958/\n[ActivityRoles]: https://modrinth.com/plugin/activityroles/\n[EconomyShopGUI]: https://www.spigotmc.org/resources/69927/\n[DiscordSchematicUploader]: https://modrinth.com/plugin/discordschematicuploader/\n[KiaiMC]: https://modrinth.com/plugin/KiaiMC/\n[SlashAdditions]: https://modrinth.com/plugin/slashadditions/\n[Custom Bans Plus]: https://www.spigotmc.org/resources/89075/\n[DiscordSRVOAuth]: https://modrinth.com/plugin/discordsrvoauth/\n[Villager Announcer]: https://modrinth.com/plugin/villager-announcer/\n[DiscordNickSync]: https://modrinth.com/plugin/discord-nick-sync\n", + "body_url": null, + "published": "2022-08-14T21:15:35.654655Z", + "updated": "2025-07-26T16:00:02.904609Z", + "approved": null, + "queued": null, + "status": "approved", + "requested_status": null, + "moderator_message": null, + "license": { + "id": "GPL-3.0-or-later", + "name": "GNU General Public License v3.0 only", + "url": null + }, + "downloads": 288560, + "followers": 567, + "categories": [ + "management", + "social", + "utility" + ], + "additional_categories": [ + "library" + ], + "loaders": [ + "bukkit", + "folia", + "paper", + "purpur", + "spigot" + ], + "versions": [ + "zP6TK1iI", + "Z4zB0DT9", + "1SLli9W4", + "D1NeVd6Q", + "mr2CijyC", + "cVT9UX3y", + "MK210KrY", + "305Ndn4O" + ], + "icon_url": "https://cdn.modrinth.com/data/UmLGoGij/icon.png", + "issues_url": "https://github.com/DiscordSRV/DiscordSRV/issues", + "source_url": "https://github.com/DiscordSRV/DiscordSRV", + "wiki_url": "https://docs.discordsrv.com/", + "discord_url": "https://discordsrv.com/discord", + "donation_urls": [ + { + "id": "github", + "platform": "Github", + "url": "https://github.com/sponsors/Scarsz" + }, + { + "id": "paypal", + "platform": "Paypal", + "url": "https://scarsz.me/donate" + } + ], + "gallery": [], + "color": 3223857, + "thread_id": "UmLGoGij", + "monetization_status": "monetized" +} \ No newline at end of file diff --git a/src/test/resources/__files/modrinth/project-griefprevention.json b/src/test/resources/__files/modrinth/project-griefprevention.json new file mode 100644 index 00000000..137d08da --- /dev/null +++ b/src/test/resources/__files/modrinth/project-griefprevention.json @@ -0,0 +1,92 @@ +{ + "client_side": "unsupported", + "server_side": "required", + "game_versions": [ + "1.17", + "1.17.1", + "1.18", + "1.18.1", + "1.18.2", + "1.19", + "1.19.1", + "1.19.2", + "1.19.3", + "1.19.4", + "1.20", + "1.20.1", + "1.20.2", + "1.20.3", + "1.20.4", + "1.20.5", + "1.20.6", + "1.21", + "1.21.1", + "1.21.2", + "1.21.3", + "1.21.4", + "1.21.5", + "1.21.6", + "1.21.7" + ], + "id": "O4o4mKaq", + "slug": "griefprevention", + "project_type": "mod", + "team": "mNkX8VBQ", + "organization": null, + "title": "GriefPrevention", + "description": "The self-service anti-griefing Bukkit plugin for Minecraft servers since 2011.", + "body": "
\n \n[![Discord][discord-shield]][discord-url]\n[![Docs][docs-shield]][docs-url]\n[![GitHub][github-shield]][github-url]\n[![IRC][irc-shield]][irc-url]\n[![Patreon][patreon-shield]][patreon-url]\n[![Downloads][downloads-shield]][bukkit-url]\n![GriefPrevention Logo][gp-logo]\n# The self-service anti-griefing plugin for Minecraft servers since 2011\n\n
\n\n\n\n## Platforms\n#### Any stable CraftBukkit derivative ([Spigot][spigot-url], [Paper][paper-url], [Pufferfish][pufferfish-url], [Purpur][purpur-url], etc.)\nGriefPrevention is targeted to support the latest available version of these platforms. Older versions can be found on our [BukkitDev][bukkit-url].\n\n** _Note: Older versions are no longer updated and may not receive any support_\n\n\n## Support\n#### [Docs][docs-url] - Configuration, Commmands, Permissions, API, and more!\n#### [Issue Tracker][issues-url] - Report any problems or bugs\n#### [GitHub Discussions][discussions-url] - New ideas, Feature requests, and Other Discussions\n#### [IRC Chat][irc-url] or [Discord][discord-url] - Talk and get help from community!\n\n\n## Addons\n#### Addons provide additional features to GriefPrevention. Some community made addons have been listed in our [GitHub Discussions][addons-url] addons section.\n---\n\n[![GriefPrevention bStats info][bstats-svg]][bstats-url]\n\n---\n[gp-logo]: https://repository-images.githubusercontent.com/68339667/9b3f7c00-ce61-11ea-82d1-208eaa0606e8\n[issues-url]: https://github.com/GriefPrevention/GriefPrevention/issues\n[discussions-url]: https://github.com/GriefPrevention/GriefPrevention/discussions\n[addons-url]: https://github.com/TechFortress/GriefPrevention/discussions/categories/addons\n[spigot-url]: https://www.spigotmc.org\n[paper-url]: https://papermc.io\n[pufferfish-url]: https://pufferfish.host/downloads\n[purpur-url]: https://purpurmc.org\n\n\n[bstats-svg]: https://bstats.org/signatures/bukkit/GriefPrevention-legacy.svg\n[bstats-url]: https://bstats.org/plugin/bukkit/GriefPrevention-legacy\n\n[irc-shield]: https://img.shields.io/badge/IRC-E8E8E8?logo=livechat&logoColor=black&style=for-the-badge\n[irc-url]: https://griefprevention.com/chat/\n\n[docs-shield]:https://img.shields.io/badge/Docs-gray?logo=readthedocs&logoColor=white&style=for-the-badge\n[docs-url]: https://docs.griefprevention.com\n\n[discord-shield]: https://img.shields.io/badge/Discord-5865F2?logo=discord&logoColor=white&style=for-the-badge\n[discord-url]: https://r.griefprevention.com/discord/\n\n[github-shield]: https://img.shields.io/badge/Source-181717?logo=github&logoColor=white&style=for-the-badge\n[github-url]: https://github.com/GriefPrevention/GriefPrevention/\n\n[patreon-shield]: https://img.shields.io/badge/Patreon-000000?logo=patreon&logoColor=white&style=for-the-badge\n[patreon-url]: https://patreon.com/robomwm/\n\n[downloads-shield]: https://img.shields.io/badge/All%20Downloads-green?style=for-the-badge\n[bukkit-url]: https://dev.bukkit.org/projects/grief-prevention/files", + "body_url": null, + "published": "2023-06-24T07:08:46.379458Z", + "updated": "2024-06-29T03:52:13.803996Z", + "approved": "2023-06-25T02:28:30.098359Z", + "queued": "2023-06-24T07:27:22.301354Z", + "status": "approved", + "requested_status": "approved", + "moderator_message": null, + "license": { + "id": "GPL-3.0-only", + "name": "GNU General Public License v3.0 only", + "url": null + }, + "downloads": 35952, + "followers": 114, + "categories": [ + "management", + "utility" + ], + "additional_categories": [ + "game-mechanics", + "social" + ], + "loaders": [ + "bukkit", + "paper", + "purpur", + "spigot" + ], + "versions": [ + "gNbluNbG", + "QYObZ1PL", + "DulfD6iE", + "S9WY4Kev", + "RQv9pNC6", + "ue7jAjJ5" + ], + "icon_url": "https://cdn.modrinth.com/data/O4o4mKaq/6aeade7ea5551c41dbe71e379ea893777356650d_96.webp", + "issues_url": "https://github.com/GriefPrevention/GriefPrevention/issues", + "source_url": "https://github.com/GriefPrevention/GriefPrevention", + "wiki_url": "https://docs.griefprevention.com/", + "discord_url": "https://r.griefprevention.com/dumcord", + "donation_urls": [ + { + "id": "patreon", + "platform": "Patreon", + "url": "https://r.robomwm.com/patreon" + } + ], + "gallery": [], + "color": 613194, + "thread_id": "O4o4mKaq", + "monetization_status": "monetized" +} \ No newline at end of file diff --git a/src/test/resources/__files/modrinth/project-viabackwards.json b/src/test/resources/__files/modrinth/project-viabackwards.json new file mode 100644 index 00000000..4ab4342f --- /dev/null +++ b/src/test/resources/__files/modrinth/project-viabackwards.json @@ -0,0 +1,554 @@ +{ + "client_side": "optional", + "server_side": "optional", + "game_versions": [ + "1.10", + "1.10.1", + "1.10.2", + "1.11", + "1.11.1", + "1.11.2", + "1.12", + "1.12.1", + "1.12.2", + "1.13", + "1.13.1", + "1.13.2", + "1.14", + "1.14.1", + "1.14.2", + "1.14.3", + "1.14.4", + "1.15", + "1.15.1", + "1.15.2", + "1.16", + "1.16.1", + "1.16.2", + "1.16.3", + "1.16.4", + "1.16.5", + "1.17", + "1.17.1", + "1.18", + "1.18.1", + "1.18.2", + "1.19", + "1.19.1", + "1.19.2", + "1.19.3", + "1.19.4", + "1.20", + "1.20.1", + "1.20.2", + "1.20.3", + "1.20.4", + "1.20.5", + "1.20.6", + "1.21", + "1.21.1", + "1.21.2", + "1.21.3", + "1.21.4", + "1.21.5", + "1.21.6", + "1.21.7", + "1.21.8" + ], + "id": "NpvuJQoq", + "slug": "viabackwards", + "project_type": "mod", + "team": "ZnAFwlvJ", + "organization": null, + "title": "ViaBackwards", + "description": "Allow older clients to connect to newer servers.", + "body": "# ViaBackwards\n\n**On Fabric, use either [ViaFabric](https://modrinth.com/mod/viafabric) or [ViaFabricPlus](https://modrinth.com/mod/viafabricplus). To override the included version**\n- in **ViaFabric**, put ViaBackwards into the `mods` folder\n- in **ViaFabricPlus**, put ViaBackwards into the `ViaFabricPlus/jars` folder\n\n**Depending on the platform and version, this also requires [ViaVersion](https://modrinth.com/mod/viaversion).**\n\nRequires Java 17. See [here](https://docs.papermc.io/misc/java-install) how to update your installed Java version. As a last resort, you can download Java 8 downgraded builds from [our ci](https://ci.viaversion.com/view/ViaBackwards/job/ViaBackwards-Java8/).\n\n## Get access to Via* with new MC version support early\n**Starting with 1.20.5, ViaVersion and ViaBackwards will only be released a day or so *after* a Minecraft update** unless the protocol changes of the update were trivial. **If you want early-access, usually days or even weeks before the final release, you can subscribe to either**:\n- [GitHub Sponsors](https://github.com/sponsors/kennytv/sponsorships?sponsor=kennytv&tier_id=385613&preview=false) (*preferred option, also comes with source code access*. Use the `/verify` command on this Discord after), or alternatively\n- [Patreon](https://www.patreon.com/kennytv/membership) (see the highest tier and make sure to link Patreon to your Discord account under Discord Settings->Connections)\n\n## Note on release channels\nIt is recommended to always use the latest beta release. Alpha builds are used for work on snapshot version compatibility or other cutting-edge changes.\n\n**Always use the same build channel across the ViaVersion, ViaBackwards, and ViaRewind projects.**\n\nReleases/Dev Builds\n-\nYou can find releases in the following places:\n\n- **Hangar (for our plugins)**: https://hangar.papermc.io/ViaVersion/ViaBackwards\n- **Modrinth (for our mods)**: https://modrinth.com/mod/viabackwards\n- **GitHub**: https://github.com/ViaVersion/ViaBackwards/releases\n\nDev builds for **all** of our projects are on our Jenkins server:\n\n- **Jenkins**: https://ci.viaversion.com/view/ViaBackwards/\n\nKnown issues\n-\n\n* 1.17+ min_y and height world values that are not 0/256 **are not supported**. Clients older than\n 1.17 will not be able to see or interact with blocks below y=0 and above y=255\n* <1.17 clients on 1.17+ servers might experience inventory desyncs on certain inventory click actions\n* Sound mappings are incomplete ([see here](https://github.com/ViaVersion/ViaBackwards/issues/326))\n\nOther Links\n-\n**Maven:** https://repo.viaversion.com\n\n**List of contributors:** https://github.com/ViaVersion/ViaBackwards/graphs/contributors\n\nSpecial Thanks\n-\n![https://www.yourkit.com/](https://www.yourkit.com/images/yklogo.png)\n\n[YourKit](https://www.yourkit.com/) supports open source projects with innovative and intelligent tools\nfor monitoring and profiling Java and .NET applications.\nYourKit is the creator of [YourKit Java Profiler](https://www.yourkit.com/java/profiler/),\n[YourKit .NET Profiler](https://www.yourkit.com/.net/profiler/),\nand [YourKit YouMonitor](https://www.yourkit.com/youmonitor/).\n", + "body_url": null, + "published": "2023-08-10T08:24:07.988560Z", + "updated": "2025-07-31T11:06:25.679444Z", + "approved": "2023-08-10T20:05:44.126439Z", + "queued": "2023-08-10T08:55:41.696555Z", + "status": "approved", + "requested_status": "approved", + "moderator_message": null, + "license": { + "id": "GPL-3.0-only", + "name": "GNU General Public License v3.0 only", + "url": "https://github.com/ViaVersion/ViaBackwards/blob/master/LICENSE" + }, + "downloads": 549213, + "followers": 449, + "categories": [ + "utility" + ], + "additional_categories": [], + "loaders": [ + "bungeecord", + "fabric", + "folia", + "paper", + "sponge", + "velocity" + ], + "versions": [ + "fd1x6MPo", + "5M3uPDEM", + "rxf6tN9x", + "thW2iOLi", + "TQMyHLct", + "fQHCj8bf", + "IO1h8sAr", + "mrea18DA", + "pLEmzm3R", + "kQklDeLj", + "KtROZF2P", + "dAsnU4qv", + "VfPuRcGx", + "rAjjScW3", + "Qkr98KwY", + "EfISRX5Y", + "1Vty4rlw", + "x7FBeyqH", + "lO6OxLQ3", + "4HRMeVtO", + "G5NnT5bp", + "F3Lcxplt", + "dMyrpTGa", + "xWt3yCv5", + "d41MwNfM", + "RkRohykX", + "G1yA5MVA", + "yjnk3oKh", + "xL5YLvcC", + "pDtiOe6f", + "NbT3CsoQ", + "6RnWSLps", + "7HzMCJA5", + "YqzA0UnO", + "GdNTLRFy", + "g5AQ5fwD", + "3g4yRTlh", + "KcPWDVhC", + "9rxpSQsy", + "GZXgPoUp", + "6G6ruYCM", + "Bvlgxo1N", + "UvACEJud", + "n869vT3r", + "9zKtygTL", + "nzDrBbFk", + "tktB1sZS", + "CAFZQBwl", + "HZymWnFH", + "lJHnsXUO", + "43w2U96o", + "OzNgFJaN", + "KEVPfina", + "YqZ0rywQ", + "lDTOgcUw", + "EXr8vLjx", + "rvzDkyO8", + "GAbf5QUO", + "K2ABi150", + "LTWhssGy", + "nLNUu1nq", + "lkq9RWDb", + "dRMYwfS7", + "wyzkfops", + "oNkZU76G", + "f0jm9Pzp", + "Tcc4BaJx", + "qaWhjniS", + "cR1Mz9Pu", + "gzCRzssZ", + "stkZYAgG", + "DmsXfwNp", + "YQUbYRIp", + "nB7Mluyl", + "1IJgmAA6", + "j0cdW43X", + "NqHz2sQu", + "NLHSkmts", + "1Xn2t3L9", + "2qjhG1Ni", + "FpFJcXFu", + "JKWHCmnG", + "T11JJJ6F", + "HBgxBHT9", + "izpsSQbR", + "OcHoQApp", + "Vu56gCEb", + "x5xUhIzt", + "fMc1KqxU", + "YktdZfVR", + "OEWFRRxG", + "JE2e1uqE", + "xT0BWkeL", + "Ms8C5OoE", + "SCv5dNTT", + "4Qnc5iVe", + "ZwqwsEMK", + "nnIudrKd", + "QSBcS1aj", + "3EEc7DME", + "g5PFg9mS", + "CH3oiY1S", + "6kKt3eO8", + "Kdc0AKjg", + "LCO2myva", + "tVJviXvP", + "1IFut6yD", + "kaqdJra2", + "AwNOjklg", + "Cs34iDRb", + "aGXP9qb8", + "1GT7riAB", + "AQEjjbyx", + "tgNH8P9C", + "XWdQUHFi", + "ZvunNwEx", + "yrKfdzVj", + "kX2bPjF5", + "q10uMj3m", + "WvNoI8Ct", + "WAzDj6no", + "1ECneg4h", + "gVDnENEf", + "MIUJiNot", + "MsnuCDle", + "jBA58JGY", + "ZQT2EkZN", + "UmFHioLO", + "mSJRMPuD", + "46vHCLic", + "jrMJ9aQV", + "6jPR8EP6", + "5lEYZcDu", + "Vz03UpLz", + "Y4Qeohpt", + "QysADh1s", + "59GUs9B1", + "ZnpobqGY", + "nWqNxXqw", + "AAIR5QtQ", + "EhJfN1ID", + "3J8rBEd4", + "c5CpW2D2", + "JZmRPJ2C", + "RoOO0vOX", + "oCiVDgwM", + "dTUpSXEp", + "FCTqcFq2", + "9gD1H3ek", + "DuoopFNV", + "6aF1KnQ3", + "oHP1ersv", + "eDsgbUeS", + "iN4Wfaan", + "cbKnn32S", + "smK3UfRI", + "TnuJGgid", + "MsuS4uXl", + "jek8EZ20", + "7A7VAFj6", + "k36WAcJB", + "Kym2whEc", + "B6bjEnwY", + "ZpDbftbW", + "ZGX95BRG", + "fLVXKI2b", + "LiLZuMNy", + "lfv9RWpL", + "ujD78p4a", + "6KQyJEuz", + "DUfm9vuF", + "fTqDb3UE", + "hXqwKcKJ", + "g8uuK2II", + "q2lbv1Kz", + "8WUho4p9", + "GXPdo9Ba", + "TeE0AIcT", + "LFbX8ZlL", + "61Qpf1wW", + "sq9Va3OM", + "yk9PGjdT", + "cC4Q7Wwr", + "mO3yE6jk", + "QFj6BdBm", + "MVUd4xd5", + "9zEaJqZw", + "ZcOtkPjT", + "zXqWDc20", + "wVo8klb1", + "iAoQxSKW", + "FmCRkuLD", + "L6W39IXY", + "Ff4rcbvl", + "8phImOKw", + "D43U9Xtk", + "rF1U18cc", + "UnYEP2Cj", + "yLxs0WXm", + "UvQAyoyd", + "k0BemufZ", + "bDdYyDoS", + "jFRrBfmz", + "dsXdhkBv", + "LLabiuGz", + "zIUuW2NT", + "b0AFeWUx", + "HqNNsA7E", + "MLpczUIu", + "fmZ67UhK", + "Ac15Spfa", + "4pzZX6yU", + "8EwXRiPi", + "TEyRxs8l", + "3mUizX4U", + "QciqDMSs", + "a7eqNX1O", + "zhI4j4fo", + "U7qVkEvg", + "UAAxbJRz", + "fe4FjxDr", + "3StMT1of", + "n31eCxZO", + "hbIrGWuq", + "9emFKffU", + "joGd2Jz9", + "KtBrvlzN", + "31TlZZ2K", + "jkeTIyVk", + "Zh2JjzBW", + "7qhwvqOI", + "FRdGRxxn", + "KTUuE0Bx", + "6Hv0NyUB", + "WDtvkDJa", + "PlPRyvYy", + "T9cxwPQ7", + "qINlc5qX", + "wPyUCgR8", + "QddzLbiq", + "HP0St2QS", + "1fu3wxn7", + "Z0N9V8iM", + "4uJGESIZ", + "6RPBOQXC", + "lBQozDsd", + "NAyFxxBU", + "KoqyXygm", + "ZL2O4kC0", + "TBcy8XRw", + "nC3rGFEx", + "rPlQ7EIB", + "SoJvI4ZA", + "LRFtpdvW", + "vyZUlozZ", + "LSyQIfB7", + "YXS4DKXc", + "DdnZp2Ma", + "pjEqSZ29", + "W0K3b9ia", + "pZezCXnz", + "9hT6QlpI", + "mj50HzuH", + "u29SeG7j", + "cnJOvgov", + "cLPMB3MY", + "jWa1fRqK", + "JLa3pOg5", + "RrgjkFTE", + "oehQkL72", + "E1LYBU7S", + "fytGxXmZ", + "lyJ6xJaP", + "CvLK6mmD", + "vM0O3rrD", + "MIDp3PVz", + "zljfwWdU", + "pK3qIkFl", + "ikT0gC8Y", + "pCqWcB5G", + "Kn9W09hI", + "eJiMAXgA", + "ij58zMfZ", + "oat6gULN", + "LShQkNSA", + "8HJTexeh", + "lEoG0v8C", + "xrtgdq8X", + "l8Qjfvep", + "P8jwgLut", + "WfzWTKTE", + "LGHFtjoL", + "bNFQPnQb", + "fjE8jzOW", + "mPT09Loo", + "yBRcX4L2", + "CCmVl30w", + "KQQMaFsa", + "QU0O2va4", + "CqFmD07u", + "v1GHEMTd", + "LUmqGg6Y", + "H3SPEf0f", + "XhxQmzEW", + "BtASHGWM", + "b6iLeK9m", + "R2caz743", + "CHiumy4z", + "8lbWQegV", + "zoVIpmI5", + "SPrE8VVb", + "NL1wGvRn", + "Zo6ovKPh", + "D0cC3UDF", + "WGN7s8Do", + "tid8v882", + "wsjoMVaY", + "5T7UkZpZ", + "5i9YVjrv", + "j0CT3uXK", + "PqccWEIp", + "wjmli6aG", + "EHCglmXL", + "h6oxCemf", + "X788SUH6", + "kwAAl5BS", + "Vz2sryhV", + "vdbm8Kkj", + "M5VzwDlW", + "CGDa3prY", + "Gr9IoswU", + "rtmgheCA", + "DesGrNkM", + "5vweIKOz", + "krmndSNQ", + "C3g2yVy8", + "6eJjhwV6", + "ZMRXhmgQ", + "SRytgDgN", + "mN4raPpy", + "oEgmoGmm", + "H28iNR9D", + "QI6on5O0", + "eMvFeqZt", + "EgetVcfc", + "MBpIPX4i", + "Xdg271Gn", + "A4ZAoekM", + "cev5Mlvr", + "6APwVLr1", + "5GEiT6dl", + "gwOS2u0n", + "APWFE2rR", + "k1pk0ORN", + "cQfwR8Kg", + "bprnc7WS", + "De6QHykA", + "Z0pQZuRn", + "EHGjmLCu", + "k3HLOUAm", + "Mwgs3g8j", + "hShraEyC", + "DwaFIGVI", + "li7AT8KA", + "YKcTefyS", + "9XZceJaH", + "XzkPhW55", + "Dy6Bgt7W", + "j6iOCThY", + "Z5r3xT5I", + "WTffXr11", + "pK2rjTtC", + "shJOZpcz", + "lepgR3Uo", + "ttbfKmTp", + "Ykergnbu", + "SdMpxRvO", + "eXuhYHxL", + "Mlh98dwp", + "E55FmqxJ", + "V3NUcO42", + "pbPgntHX", + "cuDXYAU7", + "Qw9HoMKQ", + "YTwKUGgm", + "tqSUsUQv", + "pOmoTMig", + "FWfRbMg0", + "iOJVZhW7", + "HzgwCKHZ", + "JqQsdlx2", + "5MIHZxUy", + "4yhK99Zm", + "3C2ptfp6", + "c6wU4kV7", + "ZcJfiKcO", + "CfFlomsx", + "d1TxivDS", + "3PFUelOn", + "bfVaDhZo", + "De0L5LYq", + "8glv9cBa", + "Wkxv8juz", + "KO2EZ9BJ", + "ZrVBSfkk", + "dtrTeZLl", + "jZsA8Y2U", + "VsP0lkzL", + "E8K2Ufo2", + "qecsg1Fc", + "ICVQ221Z", + "38CZdREp", + "o4Lq5Mz9", + "ZUTBvUAI", + "aWZN3MnL", + "GGyfSp1H", + "wx0s7Oix", + "6tpgm2Vm", + "5XiFO5wj", + "n7YQGght", + "cx55LXGL", + "jrVxUkQ8", + "2njhH2yF", + "T26s33Yi", + "2yubMJhd", + "qwaaaSfp", + "nCAVxHA6", + "Wtp1urWj", + "EaNkLlbu", + "2pOVwa6p", + "6kug7pdJ", + "2uTvV1C9", + "XC5aptzU", + "d4IhxGzC", + "Hwt4Rv4T", + "XEd4bGQa", + "gRwKUM29", + "TOSFdEsM", + "DpwJfo88", + "tdyh8oTV", + "WMqXufm6", + "uDZCgocL", + "jg29d9Wx" + ], + "icon_url": "https://cdn.modrinth.com/data/NpvuJQoq/c9fd86f343657c62206d5b82fee1d2b24f0b278d_96.webp", + "issues_url": "https://github.com/ViaVersion/ViaBackwards/issues", + "source_url": "https://github.com/ViaVersion/ViaBackwards", + "wiki_url": null, + "discord_url": "https://discord.gg/viaversion", + "donation_urls": [ + { + "id": "github", + "platform": "Github", + "url": "https://github.com/sponsors/kennytv" + } + ], + "gallery": [], + "color": 14476775, + "thread_id": "rSCKb1CM", + "monetization_status": "monetized" +} \ No newline at end of file diff --git a/src/test/resources/__files/modrinth/project-viaversion.json b/src/test/resources/__files/modrinth/project-viaversion.json new file mode 100644 index 00000000..54c48513 --- /dev/null +++ b/src/test/resources/__files/modrinth/project-viaversion.json @@ -0,0 +1,899 @@ +{ + "client_side": "optional", + "server_side": "optional", + "game_versions": [ + "1.8.9", + "1.9", + "1.9.1", + "1.9.2", + "1.9.3", + "1.9.4", + "1.10", + "1.10.1", + "1.10.2", + "1.11", + "1.11.1", + "1.11.2", + "1.12", + "1.12.1", + "1.12.2", + "1.13", + "1.13.1", + "1.13.2", + "1.14", + "1.14.1", + "1.14.2", + "1.14.3", + "1.14.4", + "1.15", + "1.15.1", + "1.15.2", + "1.16", + "1.16.1", + "1.16.2", + "1.16.3", + "1.16.4", + "1.16.5", + "1.17", + "1.17.1", + "1.18", + "1.18.1", + "1.18.2", + "1.19", + "1.19.1", + "1.19.2", + "1.19.3", + "1.19.4", + "1.20", + "1.20.1", + "1.20.2", + "1.20.3", + "1.20.4", + "1.20.5", + "1.20.6", + "1.21", + "1.21.1", + "1.21.2", + "1.21.3", + "1.21.4", + "1.21.5", + "1.21.6", + "1.21.7", + "1.21.8" + ], + "id": "P1OZGk5p", + "slug": "viaversion", + "project_type": "mod", + "team": "U7ClvpJK", + "organization": null, + "title": "ViaVersion", + "description": "Allow newer clients to connect to older servers.", + "body": "# ViaVersion\nAllows you to connect to servers that are older than your client version. See our [GitHub page](https://github.com/ViaVersion) for more information.\n\n**On Fabric, use either [ViaFabric](https://modrinth.com/mod/viafabric) or [ViaFabricPlus](https://modrinth.com/mod/viafabricplus). To override the included version**\n- in **ViaFabric**, put ViaVersion into the `mods` folder\n- in **ViaFabricPlus**, put ViaVersion into the `config/viafabricplus/jars` folder\n\n[Bungee](https://hangar.papermc.io/ViaVersion/ViaBungee) and [Sponge](https://modrinth.com/project/viasponge) also have their own platform implementations.\n\nRequires Java 17. See [here](https://docs.papermc.io/misc/java-install) how to update your installed Java version. As a last resort, you can download Java 8 downgraded builds from [our ci](https://ci.viaversion.com/job/ViaVersion-Java8/).\n\n## Get access to Via* with new MC version support early\n**Starting with 1.20.5, ViaVersion and ViaBackwards will only be released a day or so *after* a Minecraft update** unless the protocol changes of the update were trivial. **If you want early-access, usually days or even weeks before the final release, you can subscribe to either**:\n- [GitHub Sponsors](https://github.com/sponsors/kennytv/sponsorships?sponsor=kennytv&tier_id=385613&preview=false) (*preferred option*. Use the `/verify` command on this Discord after), or alternatively\n- [Patreon](https://www.patreon.com/kennytv/membership) (see the highest tier and make sure to link Patreon to your Discord account under Discord Settings->Connections)\n\n## Note on release channels\nIt is recommended to always use the latest beta release. Alpha builds are used for work on snapshot version compatibility or other cutting-edge changes.\n\n**Always use the same build channel across the ViaVersion, ViaBackwards, and ViaRewind projects.**\n\nReleases/Dev Builds\n--------\nYou can find official releases in the following places:\n\n- **Hangar (for our plugins)**: https://hangar.papermc.io/ViaVersion/ViaVersion\n- **Modrinth (for our mods)**: https://modrinth.com/mod/viaversion\n- **GitHub**: https://github.com/ViaVersion/ViaVersion/releases\n\nDev builds for **all** of our projects are on our Jenkins server:\n\n- **Jenkins**: https://ci.viaversion.com\n\nViaVersion as a Dependency\n----------\n\n**JavaDocs:** https://jd.viaversion.com\n\n**Maven:**\n\n```xml\n\n viaversion-repo\n https://repo.viaversion.com\n\n```\n\n```xml\n\n com.viaversion\n viaversion-api\n [4.0.0,5.0.0)\n provided\n\n```\n\n**Gradle:**\n\n```kotlin\nrepositories {\n maven(\"https://repo.viaversion.com\")\n}\n\ndependencies {\n compileOnly(\"com.viaversion:viaversion-api:VERSION\") // Replace the version\n}\n```\n\n\nResources\n--------\n\n- **[Via Mappings Generator](https://github.com/ViaVersion/Mappings)**\n- **[Mojang mappings](https://minecraft.wiki/w/Obfuscation_map)** (Thank you, Mojang, very cool)\n- **[wiki.vg](https://wiki.vg)** (Used for historic information regarding packet structure, we also contribute back)\n- **[Burger](https://github.com/Pokechu22/Burger)** (See [PAaaS](https://github.com/Matsv/Paaas))\n\n\nSpecial thanks to all our [Contributors](https://github.com/ViaVersion/ViaVersion/graphs/contributors).\n", + "body_url": null, + "published": "2023-08-10T08:23:44.094983Z", + "updated": "2025-08-02T13:11:48.811332Z", + "approved": "2023-08-10T20:05:39.927307Z", + "queued": "2023-08-10T08:59:18.887541Z", + "status": "approved", + "requested_status": "approved", + "moderator_message": null, + "license": { + "id": "GPL-3.0-or-later", + "name": "GNU General Public License v3.0 only", + "url": "https://github.com/ViaVersion/ViaVersion/blob/master/LICENSE" + }, + "downloads": 806063, + "followers": 653, + "categories": [ + "utility" + ], + "additional_categories": [], + "loaders": [ + "bungeecord", + "fabric", + "folia", + "paper", + "sponge", + "velocity" + ], + "versions": [ + "YR8Uow9d", + "vNbK6sxN", + "korEDFAR", + "OMM7FAmw", + "bGhXZNGa", + "J8l0WrUL", + "MevtJshK", + "U4kIhvH4", + "29C2sFJJ", + "7WB1MaC9", + "wCC9omTm", + "AqibpfZ1", + "bfEER1nd", + "HMwOqbwc", + "wdOJsUot", + "9GSeG8eV", + "BJGYUBhf", + "CQtazu1D", + "tvJKlFYW", + "MvsBlQBh", + "CaS9QhQj", + "2gargjcs", + "Bu7JvsQP", + "ZHr0SmI2", + "x1UhaYLR", + "mrImq3rl", + "QFqVOFPf", + "A8ayH5yF", + "gEWM5dde", + "GrXBPfFP", + "2IfqzEQ2", + "13ajstNH", + "T55MDITp", + "OmpEcjmV", + "Eh1H7gbs", + "9deXl4Mq", + "HWETus00", + "Yvvobs9t", + "zcFjTtpr", + "kaQLfPbx", + "6ZxhDg71", + "a7OfQp2R", + "XjG2GrT8", + "X9vFPnY1", + "QHaRtAym", + "eka4FTQg", + "AGaxLbNu", + "Cdmv3dmX", + "pioacjuh", + "tRGU40no", + "p4Hp8AET", + "ZobGgRfM", + "PX4RdlRR", + "MpPpebFa", + "xOS9Djim", + "hybReH52", + "BF2h8JOH", + "VtKx18Lv", + "O4hwqx34", + "bysxoMHK", + "28mrTNJM", + "1dgssdJ8", + "w1U0Wo8X", + "SU9LlYAR", + "uRKqhWfF", + "ZaHy963Q", + "QTxnxlxT", + "u9cW3YG3", + "c6QIwMgT", + "VapKXTjp", + "IPWIZnYh", + "SdlEcLVM", + "9tKBY07O", + "Ybh1d4Rb", + "uAYKIDbf", + "cxRpss1U", + "ddDgACUm", + "CyqVatta", + "IWp1J69Z", + "U06RL2H5", + "BtpgeKC1", + "5CZ4CWwW", + "hhqhAVdj", + "ADDECQmF", + "6lF0g7Il", + "xvMr8TKo", + "ZSaach7j", + "BbHCBxBV", + "3GkptTRv", + "JubXeUca", + "aG6Y4rjS", + "XDddw44l", + "E19chLL7", + "VcTi87Ce", + "v2CUdbBQ", + "pKfr4LvV", + "TV7Uenwe", + "Ur9PabgV", + "QyGaZXyk", + "stbFL2ae", + "HX9XWUVa", + "acGIRdBj", + "5iDfFCeF", + "mYei5pw3", + "AbunrsiH", + "pJAJ49jJ", + "dF74bQ7x", + "lJEXThZh", + "lT3Yop34", + "N2Hega2H", + "DXFf7cQP", + "G8yWgbwZ", + "cpsie1WI", + "cRfbxaID", + "b5tC2akA", + "KtGuLNmH", + "tjCnHFBt", + "W9cKTv6W", + "oWXr7K3b", + "elGVv5ZA", + "TLWAvS7T", + "9oi2R1TT", + "1h8z7s2n", + "ZsRJRWig", + "wF2ZaK7O", + "fSjn2nPu", + "JcWIbgOM", + "HvnEVJ43", + "7ImjBi0S", + "KmaqIvfo", + "znUydD4k", + "r0dO1LDb", + "I7mVgi25", + "N2mVJZ80", + "mYrVey0Z", + "de2vk6SO", + "SLabqxkl", + "WENx7VtO", + "TfeGwjcJ", + "4Nt8wm3Y", + "W6hn9Q0d", + "xz1TAcDA", + "XLWnH345", + "hXExPjiI", + "rKdkX4V6", + "VtEVTGOa", + "x2K5aRIP", + "BJf0r8vq", + "dcxp7kdE", + "jSgWdRCh", + "UH3MfnX5", + "vDIFcdDB", + "XNTrV4Ho", + "bUDx8Nrw", + "4tQLibfI", + "ybzCedm6", + "2nX6H80D", + "G4zgVwpW", + "ixSYAUxB", + "7dDXmzR9", + "7KKV3qUh", + "1d32qbsW", + "57KCNcai", + "5ELLKlnY", + "YORKpzNK", + "2OPzDTdU", + "cblpmAHl", + "dIdCPZMX", + "qoc693xB", + "gSQPK2uf", + "YaVwli9q", + "EhMKuUCX", + "2gkyKqGX", + "7xHha52O", + "7wqmv6h1", + "dAovr89M", + "yxxUOcP8", + "abmk74Bs", + "cUGKN7KO", + "OdP0Px1J", + "6asvkznX", + "seh3Wu6o", + "5vqpnqva", + "G6chCK9z", + "Bjo97e2m", + "IRnGUct0", + "d5mtOPSG", + "3LqBwRTo", + "ayrCzpu2", + "Ab969gQK", + "dk1h6Gp1", + "wQcaUq67", + "7EaeMOnj", + "hgRfhpbU", + "N1mgHI7G", + "LPzdedcC", + "V130cl7R", + "kxbZ6hJx", + "Ap64xWHV", + "kGzJevSf", + "ZGYltC92", + "aiDewkui", + "OC53d741", + "Ts5DxXkI", + "xupau6dv", + "bSSobEvb", + "jcD2NkFf", + "QYckuVSb", + "22VhwBwn", + "dgLv7j63", + "UWzBecyi", + "av40dBxQ", + "U6tLNvl7", + "cmOEMZK2", + "u2bifoGY", + "afVwAoCR", + "9tkKM1mj", + "zTHB64bY", + "BbKRsoE0", + "7a4Jkkv0", + "UqqUa5wd", + "Uc8YpJGg", + "IN5La3FG", + "qemnjP8p", + "rGvtfmch", + "y5dhAgHA", + "U714UGpc", + "FLkYVTgG", + "AHAbRghc", + "YvzxR9cw", + "uPbrzNC2", + "6WEMj0SI", + "xVzAm2Yf", + "KRnZaolc", + "dRqDqD5r", + "5t4DMCPg", + "JoXNozcL", + "VPRhgAeM", + "cqKTz9dM", + "YenIKyxP", + "yFsGi6Il", + "AY5a4TNH", + "J9HjllKd", + "XxiUqP6M", + "CA0ikw2f", + "m9KcSJsU", + "KXf0viv5", + "wM2DJVQX", + "jBuajMyi", + "aos75lvw", + "TorM79rk", + "uuXDjZ6t", + "P7eyNLe6", + "Dwkck6nV", + "VBIPjnt7", + "Mdkk8tl9", + "QaluzTlj", + "WEFZYz7Q", + "NZ0tC98y", + "Qa6NrT0N", + "KIFWosjV", + "hvRVReFN", + "lTUQi0d3", + "Rv3mSPct", + "cMmqQNHU", + "QYQJyOra", + "ekS2onmx", + "PmNAtPzw", + "dbHPxvSR", + "w89cAnxH", + "IlHTkmIs", + "z95tSm6U", + "uv5xztx4", + "4p7ezRv2", + "XW3l8Pds", + "oT6yD5P9", + "gWwto2Z0", + "28rVrim3", + "PADLGSC0", + "nFiE29dq", + "9cTPFT1A", + "HxVRerXg", + "8F3q7Uds", + "uRynobHq", + "5gcoBgAY", + "egCHk75w", + "xBZChvgt", + "vF7Q3WoE", + "9g5gDNSY", + "pWkI60Qk", + "OQAKnPrx", + "A0Wpk6vR", + "5n4j2QDe", + "BXKsmDCj", + "ditiUWAx", + "kCt8Vtda", + "fggKumom", + "Q6fplkEg", + "V6WCGAJV", + "v7YVKtZa", + "jpIQ8qzR", + "P9Uex70t", + "76o9hrcM", + "JU3kTmaB", + "VoF7sLeH", + "YKqX2m0D", + "3mfuQYy9", + "gskwMe9v", + "rFMu1IGq", + "myWZFbne", + "L9kZbp2C", + "fd1Y26aW", + "oTtEzGb2", + "9LEXqKUr", + "trLxlGat", + "3hc96msj", + "DNKRBNmr", + "HHQBhSAs", + "12XXUo36", + "AxBIFT0g", + "tiKgVVNp", + "c8kPwhK1", + "qr2t6wUX", + "U6gi5ToG", + "q2keBtBd", + "bELLsQGM", + "7sL6kssS", + "5TH7ik1r", + "DO94vDLY", + "ehNomstN", + "cZ4MiQgH", + "iONEuROq", + "Wd5dvbMK", + "SzOEw3vY", + "Odljd5EL", + "OH3iCg93", + "dOHy9zQb", + "1gzArrnX", + "xGQhobcA", + "LGOJBePq", + "FewAo2P4", + "CsVSgIQj", + "naoztfLJ", + "W7929Eqk", + "hiefx71A", + "u3kiH9OH", + "VNjjvsr5", + "qKSqy17G", + "FkOmZyBY", + "HFipDzEy", + "RcUGKEEk", + "THaPTO7M", + "oOo5JLst", + "SqGDbize", + "PpFhBRHe", + "Q7dIA9zC", + "MyPTbQhh", + "V2LRndkx", + "sVZeAWnQ", + "TFqVjJEj", + "o96MovMD", + "ZYyOGhXN", + "aFmBJ1oF", + "A38UWZ1A", + "NCSOjV1J", + "cigcpEyA", + "HKQsl2fC", + "uSymaYRP", + "Uti6fBUp", + "K1AhqPRj", + "cxhiTzdD", + "PvaiV7Nr", + "WGiWZgWd", + "5NRgKXzi", + "WCtbqmK8", + "KCN5G03j", + "qDylNeMB", + "VbjL4WqL", + "HmFmGW3n", + "yqt1caFd", + "xE6aec17", + "COlI25ti", + "glgeEezt", + "6UBWU4Ah", + "kdE0yo1B", + "lHQHdmgy", + "D0X6MmyH", + "koUd5OQ7", + "YLRny7WQ", + "KBbj6Zgn", + "EhEw31q9", + "CmzwtIis", + "WxS81nAl", + "2EHJuSSc", + "NoXn5Pch", + "m7390zwN", + "u4pKWlxV", + "4y0bXPZV", + "n5mM66Gq", + "wRrNlMzw", + "GSYnFEpg", + "fCCeJk7P", + "h2e1lLH8", + "aI0Ha14H", + "Wir6t4xN", + "7FgVulta", + "WVZZOYup", + "tFNLJIFv", + "CVyC9Fgg", + "Dx7tALsK", + "Z90xf5P1", + "H9TGgulA", + "6kgQxT8j", + "odD8ZBYG", + "kUFp8rle", + "GavTp4D8", + "Boh5DWqH", + "FnIO0AKf", + "rd4CR16F", + "UKNZSM2l", + "mAXMDukm", + "dTAZSAb3", + "5d2gQAxX", + "VaR3ZJRL", + "qp1u7Vs2", + "Z0t38n0J", + "yrpaHyTG", + "nRxdRYLB", + "ZqYqmwCR", + "kbQz1izl", + "fojFzCyd", + "d38kfbGF", + "r4KfybkQ", + "yJxZzxQM", + "Pm73bk60", + "1BR0MiTW", + "Sr9buQyP", + "543Ndqg7", + "HqwPj807", + "BEVKz1Ef", + "QXzACaav", + "g8RwhHrc", + "BHtsFVQO", + "1cYNiLxh", + "hopu9m5q", + "IBXaaY01", + "dJ6M6D6s", + "QSYf240I", + "Pcj4DJYS", + "9yYcAHw0", + "Kjw7EQIw", + "iY63W6pV", + "YTz6dbWO", + "hjSwf0mg", + "PW1xPUJG", + "eWnC4hgY", + "RHu8FQmd", + "8Jv3nCHv", + "i0qVvMmh", + "uIVDLIYA", + "SQoaz75o", + "bmpz8dHS", + "qUnWX5Y1", + "2xaSgalU", + "sOSZXuTS", + "tAqiuIkj", + "QqnErqTG", + "SmfO3c6j", + "jld4WjCo", + "eNZmRwyu", + "XNugo37B", + "bSffyebl", + "5537dOeT", + "CtTDLsaD", + "o6rrTddz", + "zxQdfyqs", + "ZsX1PqVv", + "CyuQOOuv", + "9uMLIc6w", + "R6MNWQmm", + "NqtPPe14", + "AbTJXlLj", + "5jAqVCsx", + "U5H0nXE2", + "nzRNag2p", + "Kh6dQjg7", + "vvJAmMLl", + "ahfXZD04", + "vuebz4fV", + "RbeeONgo", + "fljwFexP", + "tHcXTImI", + "fbkqR3xw", + "WRoTpvxE", + "JgKgTyu8", + "WXdlLEA4", + "nkMvxbdA", + "7HIDz6eH", + "5AWpDi7M", + "21VhY22d", + "tQETWlqh", + "5akJu9f4", + "6ZYkDOXx", + "cdREc6hd", + "NdvW4z1c", + "qRaBomoo", + "zyGFFXmp", + "k3mTP16J", + "80p2C9h9", + "IoQQlUWG", + "vbKYDyk4", + "JEjVrkym", + "6Mkk5kNT", + "qKzyMBLH", + "8VcjmY6W", + "WNOdUuKF", + "pmXPieWB", + "np8EFQS6", + "Jtxu5UTT", + "amRMBT6O", + "KKtJzFmD", + "5OGacVsw", + "FSf9Fqar", + "HuncyNFM", + "gK9oE1tm", + "L7WrbYfV", + "S9TEbb6I", + "u2VSikwi", + "ye4vbSqJ", + "paZUzdit", + "xmrv9SbR", + "MYxnCoqj", + "Cq1eXF3c", + "hYgihEWe", + "GTcOzQNj", + "MQ1KFirI", + "IdxxVg6b", + "dUmT22SN", + "dtihaSUq", + "q8zzfSOx", + "HHX7jl9w", + "Rl64p8ON", + "QhQyvRWS", + "gCeZYBfA", + "bbVuKw4p", + "W38pNn4T", + "wJ5qwAmV", + "oBhHZwp4", + "UJrw8Zxn", + "7mkZeJaU", + "6QWKRE70", + "L15n7AMu", + "EkrRAVfa", + "sVA8Vvtm", + "lAh7NCOA", + "jZGn0iyJ", + "1xGph9Wv", + "s7oLftDK", + "V1YOj7Ii", + "yGC3QVbn", + "YloWqQNW", + "6cxQJDRF", + "ivC42RGK", + "wt7zyWQn", + "CwGKSgZ4", + "oXba1Ojw", + "dMRHNKUg", + "AtCRKoZv", + "G7tsm1xF", + "Wx48B1gz", + "bnt5DVT7", + "kXJSbNlW", + "QyT2cjtH", + "oTzTGC72", + "vetOLdCC", + "ELGwNWH5", + "siu7JuSE", + "5B9LhBIg", + "wMQKGNXD", + "DroiJjDw", + "RiYptrBz", + "ARMXkv2r", + "uQ6OPK6r", + "ffAFJrjN", + "JaQea00b", + "oDNCT8P4", + "geAdwXB0", + "XZ6GHAu1", + "UCQRctkm", + "DKzv3PUK", + "gn07KcF8", + "yCed0skl", + "DhCBQjba", + "TrAtzb8V", + "AUsNSsKS", + "L0RH3Ks7", + "1c6VQUE3", + "grkBvnMt", + "YLpBlgPP", + "PDLDxtFA", + "MC0q4Low", + "mgkpK4rj", + "EfNp6SMH", + "25SGDn6J", + "cPDkwex1", + "sztUBMs0", + "JiMICHee", + "RfglG0FB", + "E9igD53h", + "rPLfsSW6", + "WsKoKsIO", + "2mrjg2ur", + "brElfvb4", + "jIqGWFP4", + "uyrvt6JR", + "scBqPtsp", + "DXfSdX9U", + "3tr3uzok", + "O6by7D9R", + "ju9aZMoM", + "nN5coYHw", + "Lli49VrJ", + "jbXugTWc", + "edrCfcba", + "BoG3z94P", + "9zW2nwlR", + "BBJEamcK", + "CZtFN1EU", + "qrRmX8uz", + "Osfu3H3C", + "R1gOqXAA", + "HLAzU2WN", + "CQrxHH1J", + "TqsYfCxI", + "tvyUfGES", + "D8zA9ThN", + "KYLOGvFS", + "KVZaQXnF", + "euVQ4l1L", + "gKKuwYIF", + "UtEK9Bxn", + "Qk0B1ssC", + "FB1AZx0z", + "p5sXOzZW", + "61HuNLT4", + "IXnXyvOs", + "bDaVrakt", + "josVFwsb", + "9ycs3byg", + "fBxQ2wZW", + "4Mx5gbm6", + "OnoI4IFd", + "v5VrmMQr", + "Ndiee4mC", + "iXg6Ghq4", + "tyI6b1fN", + "HUfwvCw3", + "em1wK1d9", + "fObqLaMu", + "gGDz6lhG", + "4B0YXGBN", + "S0HcO4Ih", + "n3fPszKs", + "Pj0O0zXU", + "ufFRCp2b", + "USwROm0W", + "ajeLa46N", + "Wry9t810", + "99lBFmGO", + "Dj7o5I8u", + "w1kHc012", + "t8Xu85W6", + "DAjLtP9P", + "J2Hk0vVC", + "WUqsrpV6", + "YTkVYnhy", + "X19qO6T7", + "f4bsc9GM", + "wFmDP5G9", + "s9CnHFfY", + "kPVTXB7k", + "C12OfZvq", + "YOHThzzm", + "ZbFOsGG3", + "nujLGyA7", + "ma1Exjyz", + "2mitYMYy", + "Us5JjmMX", + "ErTrHUQB", + "X9ooUNKa", + "j5xm9wM5", + "mNEL92yD", + "mo9iAvWf", + "LHlTkQo1", + "dvFB2xTT", + "IFqbikLB", + "hKmPOTRe", + "cdC9vQSF", + "M0JEvBb8", + "qDaavkC8", + "rqUxXrEc", + "ubZdVXvm", + "pEYojuCc", + "xJepxW1s", + "qzzWpRMR", + "A2Gp2bUR", + "AAVqqXUe", + "gErMhnJE", + "ahA4SQuF", + "T50Dp0ao", + "9AyzkOpT", + "QW7KEgok", + "GPxgun3b", + "kyy4N4jk", + "Z26NPAvE", + "V1hNGC1X", + "KSbAhOxy", + "BXmLgC4E", + "rdlCqeie", + "Ue2GdA1l", + "ILcPTM9V", + "rRQN0rgP", + "CeSaifRQ", + "WPQF5Kff", + "dPxyW3ul", + "iYg8jtFg", + "sL4eNbJs", + "dOkObfYM", + "A0SCIXM8", + "4VZfPXbK", + "CAcPjOnj", + "5sgYqLEC", + "nH8qlWC9", + "jQ1QUqBv", + "PutCPQ93", + "A0vAW4Ml", + "c7qUCKzX", + "AybH7wix", + "wMqhJcIA", + "QuYpDYCR", + "PhVjA6I4", + "j9MgimOx", + "JAdyEcEG", + "eaJAcT7k", + "6JJtnjbu", + "aeYBqqma", + "AQo8yEm8", + "nBpCAN2m", + "VCc5jinM", + "eiUNiUow", + "7V0qikdZ", + "5xUo716w", + "pIEEcErE", + "2pdYFETa", + "Vwq3jEdw", + "9LCube8L", + "fYYFUa8i", + "wRYTZnY7", + "5wZkGWxe", + "T6r3rk4R", + "rYLiX9Cb", + "AhROp35W", + "h98pV3Q4", + "OfDnX6EL", + "2njkyDC9", + "xHYZvs9Z", + "YetGFH3D", + "OYkGD4xF", + "FhQHptXf", + "3Im2ITe6", + "nQ22Z3jA", + "URP8prrm", + "2Ht6PUhg", + "FFzky9qo", + "e4eSuLAu", + "gEaUuobS", + "bnjiE6P3", + "xO0CRp7R", + "d2jzNkId", + "bZuoThdC", + "g0LZEAJl", + "zavgUaJm" + ], + "icon_url": "https://cdn.modrinth.com/data/P1OZGk5p/ad14260a7308dc9e4c3385f3f6b5bdabfe17f295_96.webp", + "issues_url": "https://github.com/ViaVersion/ViaVersion/issues", + "source_url": "https://github.com/ViaVersion/ViaVersion", + "wiki_url": null, + "discord_url": "https://discord.gg/viaversion", + "donation_urls": [ + { + "id": "github", + "platform": "Github", + "url": "https://github.com/sponsors/kennytv" + } + ], + "gallery": [], + "color": 14212575, + "thread_id": "cYGU3422", + "monetization_status": "monetized" +} \ No newline at end of file