Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion src/main/java/me/itzg/helpers/modrinth/Loader.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ public enum Loader {
pufferfish("plugins", paper),
purpur("plugins", paper),
bungeecord("plugins", null),
velocity("plugins", null);
velocity("plugins", null),
datapack(null, null);

private final String type;
private final Loader compatibleWith;
Expand Down
11 changes: 7 additions & 4 deletions src/main/java/me/itzg/helpers/modrinth/ModrinthApiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,11 @@ public Mono<List<ResolvedProject>> bulkGetProjects(Stream<ProjectRef> projectRef
public Mono<Version> resolveProjectVersion(Project project, ProjectRef projectRef,
@Nullable Loader loader, String gameVersion,
VersionType defaultVersionType) {

final Loader loaderToQuery = projectRef.isDatapack() ? Loader.datapack : loader;

if (projectRef.hasVersionName()) {
return getVersionsForProject(project.getId(), loader, gameVersion)
return getVersionsForProject(project.getId(), loaderToQuery, gameVersion)
.flatMap(versions ->
Mono.justOrEmpty(versions.stream()
.filter(version ->
Expand All @@ -137,12 +140,12 @@ public Mono<Version> resolveProjectVersion(Project project, ProjectRef projectRe
));
}
if (projectRef.hasVersionType()) {
return getVersionsForProject(project.getId(), loader, gameVersion)
return getVersionsForProject(project.getId(), loaderToQuery, gameVersion)
.mapNotNull(versions -> pickVersion(project, versions, projectRef.getVersionType()));
} else if (projectRef.hasVersionId()) {
return getVersionFromId(projectRef.getVersionId());
} else {
return getVersionsForProject(project.getId(), loader, gameVersion)
return getVersionsForProject(project.getId(), loaderToQuery, gameVersion)
.mapNotNull(versions -> pickVersion(project, versions, defaultVersionType));
}
}
Expand Down Expand Up @@ -190,7 +193,7 @@ public Mono<List<Version>> getVersionsForProject(String projectIdOrSlug,
);
}

private List<String> expandCompatibleLoaders(Loader loader) {
private List<String> expandCompatibleLoaders(@Nullable Loader loader) {
if (loader == null) {
return null;
}
Expand Down
62 changes: 52 additions & 10 deletions src/main/java/me/itzg/helpers/modrinth/ModrinthCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import me.itzg.helpers.http.Fetch;
import me.itzg.helpers.http.SharedFetchArgs;
import me.itzg.helpers.json.ObjectMappers;
import me.itzg.helpers.modrinth.model.Constants;
import me.itzg.helpers.modrinth.model.DependencyType;
import me.itzg.helpers.modrinth.model.Project;
import me.itzg.helpers.modrinth.model.ProjectType;
Expand All @@ -42,6 +43,7 @@
@Slf4j
public class ModrinthCommand implements Callable<Integer> {

public static final String DATAPACKS_SUBDIR = "datapacks";
@Option(names = "--projects", description = "Project ID or Slug",
split = SPLIT_COMMA_NL, splitSynopsisLabel = SPLIT_SYNOPSIS_COMMA_NL,
paramLabel = "id|slug"
Expand Down Expand Up @@ -78,6 +80,11 @@ public enum DownloadDependencies {
)
String baseUrl;

@Option(names = "--world-directory", defaultValue = "${env:LEVEL:-world}",
description = "Used for datapacks, a path relative to the output directory or an absolute path\nDefault: ${DEFAULT-VALUE}"
)
Path worldDirectory;

@ArgGroup(exclusive = false)
SharedFetchArgs sharedFetchArgs = new SharedFetchArgs();

Expand Down Expand Up @@ -115,7 +122,9 @@ private List<Path> processProjects(List<String> projects) {
.defaultIfEmpty(Collections.emptyList())
.block()
.stream()
.flatMap(resolvedProject -> processProject(modrinthApiClient, resolvedProject.getProjectRef(), resolvedProject.getProject()))
.flatMap(resolvedProject -> processProject(
modrinthApiClient, resolvedProject.getProjectRef(), resolvedProject.getProject()
))
.collect(Collectors.toList());
}
}
Expand Down Expand Up @@ -201,14 +210,31 @@ private Version pickVersion(List<Version> versions, VersionType versionType) {
return null;
}

private Path download(ProjectType projectType, VersionFile versionFile) {
if (projectType != ProjectType.mod) {
throw new InvalidParameterException("Only mod project types can be downloaded for now");
}
private Path download(boolean isDatapack, VersionFile versionFile) {
final Path outPath;
try {
outPath = Files.createDirectories(outputDirectory.resolve(loader.getType()))
.resolve(versionFile.getFilename());
if (!isDatapack) {
outPath = Files.createDirectories(outputDirectory
.resolve(loader.getType())
)
.resolve(versionFile.getFilename());
}
else {
if (worldDirectory.isAbsolute()) {
outPath = Files.createDirectories(worldDirectory
.resolve(DATAPACKS_SUBDIR)
)
.resolve(versionFile.getFilename());
}
else {
outPath = Files.createDirectories(outputDirectory
.resolve(worldDirectory)
.resolve(DATAPACKS_SUBDIR)
)
.resolve(versionFile.getFilename());
}
}

} catch (IOException e) {
throw new RuntimeException("Creating mods directory", e);
}
Expand Down Expand Up @@ -238,7 +264,14 @@ private List<Version> getVersionsForProject(ModrinthApiClient modrinthApiClient,


private Stream<Path> processProject(ModrinthApiClient modrinthApiClient, ProjectRef projectRef, Project project) {
log.debug("Starting with projectRef={}", projectRef);
if (project.getProjectType() != ProjectType.mod) {
throw new InvalidParameterException(
String.format("Requested project '%s' is not a mod, but has type %s",
project.getTitle(), project.getProjectType()
));
}

log.debug("Starting with project='{}' slug={}", project.getTitle(), project.getSlug());

if (projectsProcessed.add(project.getId())) {
final Version version;
Expand All @@ -256,13 +289,15 @@ private Stream<Path> processProject(ModrinthApiClient modrinthApiClient, Project
throw new GenericException(String.format("Project %s has no files declared", project.getSlug()));
}

final boolean isDatapack = isDatapack(version);

return Stream.concat(
Stream.of(version),
expandDependencies(modrinthApiClient, version)
)
.map(ModrinthApiClient::pickVersionFile)
.map(versionFile -> download(project.getProjectType(), versionFile))
.flatMap(this::expandIfZip);
.map(versionFile -> download(isDatapack, versionFile))
.flatMap(downloadedFile -> !isDatapack ? expandIfZip(downloadedFile) : Stream.empty());
}
else {
throw new InvalidParameterException(
Expand All @@ -274,6 +309,13 @@ private Stream<Path> processProject(ModrinthApiClient modrinthApiClient, Project
return Stream.empty();
}

private boolean isDatapack(Version version) {
return
version.getLoaders() != null
&& version.getLoaders().size() == 1
&& version.getLoaders().get(0).equals(Constants.LOADER_DATAPACK);
}

/**
* If downloadedFile ends in .zip, then expand it, return its files and given file.
*
Expand Down
36 changes: 27 additions & 9 deletions src/main/java/me/itzg/helpers/modrinth/ProjectRef.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,38 @@
import lombok.ToString;
import me.itzg.helpers.errors.InvalidParameterException;
import me.itzg.helpers.modrinth.model.VersionType;
import org.jetbrains.annotations.Nullable;

@Getter
@ToString
public class ProjectRef {
private static final Pattern VERSIONS = Pattern.compile("[a-zA-Z0-9]{8}");
private final static Pattern MODPACK_PAGE_URL = Pattern.compile(
private static final Pattern MODPACK_PAGE_URL = Pattern.compile(
"https://modrinth.com/modpack/(?<slug>.+?)(/version/(?<versionName>.+))?"
);
private static final Pattern PROJECT_REF = Pattern.compile("(?<datapack>datapack:)?(?<idSlug>[^:]+?)(:(?<version>[^:]+))?");

private final String idOrSlug;
private final boolean datapack;

final String idOrSlug;
/**
* Either a remote URI or a file URI for a locally provided file
*/
final URI projectUri;
final VersionType versionType;
final String versionId;
final String versionName;
private final URI projectUri;
private final VersionType versionType;
private final String versionId;
private final String versionName;

public static ProjectRef parse(String projectRef) {
final String[] projectRefParts = projectRef.split(":", 2);
final Matcher m = PROJECT_REF.matcher(projectRef);
if (!m.matches()) {
throw new InvalidParameterException("Invalid project reference: " + projectRef);
}

return new ProjectRef(projectRefParts[0],
projectRefParts.length > 1 ? projectRefParts[1] : null
return new ProjectRef(
m.group("idSlug"),
m.group("version"),
m.group("datapack") != null
);
}

Expand All @@ -44,7 +53,15 @@ public static ProjectRef parse(String projectRef) {
* @param version can be a {@link VersionType}, ID, or name/number
*/
public ProjectRef(String projectSlug, String version) {
this(projectSlug, version, false);
}

/**
* @param version can be a {@link VersionType}, ID, or name/number
*/
public ProjectRef(String projectSlug, @Nullable String version, boolean datapack) {
this.idOrSlug = projectSlug;
this.datapack = datapack;
this.projectUri = null;
this.versionType = parseVersionType(version);
if (this.versionType == null) {
Expand All @@ -64,6 +81,7 @@ public ProjectRef(String projectSlug, String version) {
}

public ProjectRef(URI projectUri, String versionId) {
this.datapack = false;
this.projectUri = projectUri;

final String filename = extractFilename(projectUri);
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/me/itzg/helpers/modrinth/model/Constants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package me.itzg.helpers.modrinth.model;

public class Constants {

public static final String LOADER_DATAPACK = "datapack";

}
24 changes: 13 additions & 11 deletions src/main/java/me/itzg/helpers/modrinth/model/Version.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,31 @@

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;

import java.time.Instant;
import java.util.List;
import lombok.Data;

@Data
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class Version {
String id;

String projectId;
private String id;

private String projectId;

private String name;

String name;
private Instant datePublished;

Instant datePublished;
private String versionNumber;

String versionNumber;
private VersionType versionType;

VersionType versionType;
private List<VersionFile> files;

List<VersionFile> files;
private List<VersionDependency> dependencies;

List<VersionDependency> dependencies;
private List<String> gameVersions;

List<String> gameVersions;
private List<String> loaders;
}
Loading