Skip to content

Commit 87a056c

Browse files
authored
modrinth: support datapack retrieval (#478)
1 parent db89f8b commit 87a056c

File tree

8 files changed

+248
-35
lines changed

8 files changed

+248
-35
lines changed

src/main/java/me/itzg/helpers/modrinth/Loader.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ public enum Loader {
1414
pufferfish("plugins", paper),
1515
purpur("plugins", paper),
1616
bungeecord("plugins", null),
17-
velocity("plugins", null);
17+
velocity("plugins", null),
18+
datapack(null, null);
1819

1920
private final String type;
2021
private final Loader compatibleWith;

src/main/java/me/itzg/helpers/modrinth/ModrinthApiClient.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,11 @@ public Mono<List<ResolvedProject>> bulkGetProjects(Stream<ProjectRef> projectRef
125125
public Mono<Version> resolveProjectVersion(Project project, ProjectRef projectRef,
126126
@Nullable Loader loader, String gameVersion,
127127
VersionType defaultVersionType) {
128+
129+
final Loader loaderToQuery = projectRef.isDatapack() ? Loader.datapack : loader;
130+
128131
if (projectRef.hasVersionName()) {
129-
return getVersionsForProject(project.getId(), loader, gameVersion)
132+
return getVersionsForProject(project.getId(), loaderToQuery, gameVersion)
130133
.flatMap(versions ->
131134
Mono.justOrEmpty(versions.stream()
132135
.filter(version ->
@@ -137,12 +140,12 @@ public Mono<Version> resolveProjectVersion(Project project, ProjectRef projectRe
137140
));
138141
}
139142
if (projectRef.hasVersionType()) {
140-
return getVersionsForProject(project.getId(), loader, gameVersion)
143+
return getVersionsForProject(project.getId(), loaderToQuery, gameVersion)
141144
.mapNotNull(versions -> pickVersion(project, versions, projectRef.getVersionType()));
142145
} else if (projectRef.hasVersionId()) {
143146
return getVersionFromId(projectRef.getVersionId());
144147
} else {
145-
return getVersionsForProject(project.getId(), loader, gameVersion)
148+
return getVersionsForProject(project.getId(), loaderToQuery, gameVersion)
146149
.mapNotNull(versions -> pickVersion(project, versions, defaultVersionType));
147150
}
148151
}
@@ -190,7 +193,7 @@ public Mono<List<Version>> getVersionsForProject(String projectIdOrSlug,
190193
);
191194
}
192195

193-
private List<String> expandCompatibleLoaders(Loader loader) {
196+
private List<String> expandCompatibleLoaders(@Nullable Loader loader) {
194197
if (loader == null) {
195198
return null;
196199
}

src/main/java/me/itzg/helpers/modrinth/ModrinthCommand.java

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import me.itzg.helpers.http.Fetch;
2727
import me.itzg.helpers.http.SharedFetchArgs;
2828
import me.itzg.helpers.json.ObjectMappers;
29+
import me.itzg.helpers.modrinth.model.Constants;
2930
import me.itzg.helpers.modrinth.model.DependencyType;
3031
import me.itzg.helpers.modrinth.model.Project;
3132
import me.itzg.helpers.modrinth.model.ProjectType;
@@ -42,6 +43,7 @@
4243
@Slf4j
4344
public class ModrinthCommand implements Callable<Integer> {
4445

46+
public static final String DATAPACKS_SUBDIR = "datapacks";
4547
@Option(names = "--projects", description = "Project ID or Slug",
4648
split = SPLIT_COMMA_NL, splitSynopsisLabel = SPLIT_SYNOPSIS_COMMA_NL,
4749
paramLabel = "id|slug"
@@ -78,6 +80,11 @@ public enum DownloadDependencies {
7880
)
7981
String baseUrl;
8082

83+
@Option(names = "--world-directory", defaultValue = "${env:LEVEL:-world}",
84+
description = "Used for datapacks, a path relative to the output directory or an absolute path\nDefault: ${DEFAULT-VALUE}"
85+
)
86+
Path worldDirectory;
87+
8188
@ArgGroup(exclusive = false)
8289
SharedFetchArgs sharedFetchArgs = new SharedFetchArgs();
8390

@@ -115,7 +122,9 @@ private List<Path> processProjects(List<String> projects) {
115122
.defaultIfEmpty(Collections.emptyList())
116123
.block()
117124
.stream()
118-
.flatMap(resolvedProject -> processProject(modrinthApiClient, resolvedProject.getProjectRef(), resolvedProject.getProject()))
125+
.flatMap(resolvedProject -> processProject(
126+
modrinthApiClient, resolvedProject.getProjectRef(), resolvedProject.getProject()
127+
))
119128
.collect(Collectors.toList());
120129
}
121130
}
@@ -201,14 +210,31 @@ private Version pickVersion(List<Version> versions, VersionType versionType) {
201210
return null;
202211
}
203212

204-
private Path download(ProjectType projectType, VersionFile versionFile) {
205-
if (projectType != ProjectType.mod) {
206-
throw new InvalidParameterException("Only mod project types can be downloaded for now");
207-
}
213+
private Path download(boolean isDatapack, VersionFile versionFile) {
208214
final Path outPath;
209215
try {
210-
outPath = Files.createDirectories(outputDirectory.resolve(loader.getType()))
211-
.resolve(versionFile.getFilename());
216+
if (!isDatapack) {
217+
outPath = Files.createDirectories(outputDirectory
218+
.resolve(loader.getType())
219+
)
220+
.resolve(versionFile.getFilename());
221+
}
222+
else {
223+
if (worldDirectory.isAbsolute()) {
224+
outPath = Files.createDirectories(worldDirectory
225+
.resolve(DATAPACKS_SUBDIR)
226+
)
227+
.resolve(versionFile.getFilename());
228+
}
229+
else {
230+
outPath = Files.createDirectories(outputDirectory
231+
.resolve(worldDirectory)
232+
.resolve(DATAPACKS_SUBDIR)
233+
)
234+
.resolve(versionFile.getFilename());
235+
}
236+
}
237+
212238
} catch (IOException e) {
213239
throw new RuntimeException("Creating mods directory", e);
214240
}
@@ -238,7 +264,14 @@ private List<Version> getVersionsForProject(ModrinthApiClient modrinthApiClient,
238264

239265

240266
private Stream<Path> processProject(ModrinthApiClient modrinthApiClient, ProjectRef projectRef, Project project) {
241-
log.debug("Starting with projectRef={}", projectRef);
267+
if (project.getProjectType() != ProjectType.mod) {
268+
throw new InvalidParameterException(
269+
String.format("Requested project '%s' is not a mod, but has type %s",
270+
project.getTitle(), project.getProjectType()
271+
));
272+
}
273+
274+
log.debug("Starting with project='{}' slug={}", project.getTitle(), project.getSlug());
242275

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

292+
final boolean isDatapack = isDatapack(version);
293+
259294
return Stream.concat(
260295
Stream.of(version),
261296
expandDependencies(modrinthApiClient, version)
262297
)
263298
.map(ModrinthApiClient::pickVersionFile)
264-
.map(versionFile -> download(project.getProjectType(), versionFile))
265-
.flatMap(this::expandIfZip);
299+
.map(versionFile -> download(isDatapack, versionFile))
300+
.flatMap(downloadedFile -> !isDatapack ? expandIfZip(downloadedFile) : Stream.empty());
266301
}
267302
else {
268303
throw new InvalidParameterException(
@@ -274,6 +309,13 @@ private Stream<Path> processProject(ModrinthApiClient modrinthApiClient, Project
274309
return Stream.empty();
275310
}
276311

312+
private boolean isDatapack(Version version) {
313+
return
314+
version.getLoaders() != null
315+
&& version.getLoaders().size() == 1
316+
&& version.getLoaders().get(0).equals(Constants.LOADER_DATAPACK);
317+
}
318+
277319
/**
278320
* If downloadedFile ends in .zip, then expand it, return its files and given file.
279321
*

src/main/java/me/itzg/helpers/modrinth/ProjectRef.java

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,38 @@
1313
import lombok.ToString;
1414
import me.itzg.helpers.errors.InvalidParameterException;
1515
import me.itzg.helpers.modrinth.model.VersionType;
16+
import org.jetbrains.annotations.Nullable;
1617

1718
@Getter
1819
@ToString
1920
public class ProjectRef {
2021
private static final Pattern VERSIONS = Pattern.compile("[a-zA-Z0-9]{8}");
21-
private final static Pattern MODPACK_PAGE_URL = Pattern.compile(
22+
private static final Pattern MODPACK_PAGE_URL = Pattern.compile(
2223
"https://modrinth.com/modpack/(?<slug>.+?)(/version/(?<versionName>.+))?"
2324
);
25+
private static final Pattern PROJECT_REF = Pattern.compile("(?<datapack>datapack:)?(?<idSlug>[^:]+?)(:(?<version>[^:]+))?");
26+
27+
private final String idOrSlug;
28+
private final boolean datapack;
2429

25-
final String idOrSlug;
2630
/**
2731
* Either a remote URI or a file URI for a locally provided file
2832
*/
29-
final URI projectUri;
30-
final VersionType versionType;
31-
final String versionId;
32-
final String versionName;
33+
private final URI projectUri;
34+
private final VersionType versionType;
35+
private final String versionId;
36+
private final String versionName;
3337

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

37-
return new ProjectRef(projectRefParts[0],
38-
projectRefParts.length > 1 ? projectRefParts[1] : null
44+
return new ProjectRef(
45+
m.group("idSlug"),
46+
m.group("version"),
47+
m.group("datapack") != null
3948
);
4049
}
4150

@@ -44,7 +53,15 @@ public static ProjectRef parse(String projectRef) {
4453
* @param version can be a {@link VersionType}, ID, or name/number
4554
*/
4655
public ProjectRef(String projectSlug, String version) {
56+
this(projectSlug, version, false);
57+
}
58+
59+
/**
60+
* @param version can be a {@link VersionType}, ID, or name/number
61+
*/
62+
public ProjectRef(String projectSlug, @Nullable String version, boolean datapack) {
4763
this.idOrSlug = projectSlug;
64+
this.datapack = datapack;
4865
this.projectUri = null;
4966
this.versionType = parseVersionType(version);
5067
if (this.versionType == null) {
@@ -64,6 +81,7 @@ public ProjectRef(String projectSlug, String version) {
6481
}
6582

6683
public ProjectRef(URI projectUri, String versionId) {
84+
this.datapack = false;
6785
this.projectUri = projectUri;
6886

6987
final String filename = extractFilename(projectUri);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package me.itzg.helpers.modrinth.model;
2+
3+
public class Constants {
4+
5+
public static final String LOADER_DATAPACK = "datapack";
6+
7+
}

src/main/java/me/itzg/helpers/modrinth/model/Version.java

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,31 @@
22

33
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
44
import com.fasterxml.jackson.databind.annotation.JsonNaming;
5-
import lombok.Data;
6-
75
import java.time.Instant;
86
import java.util.List;
7+
import lombok.Data;
98

109
@Data
1110
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
1211
public class Version {
13-
String id;
1412

15-
String projectId;
13+
private String id;
14+
15+
private String projectId;
16+
17+
private String name;
1618

17-
String name;
19+
private Instant datePublished;
1820

19-
Instant datePublished;
21+
private String versionNumber;
2022

21-
String versionNumber;
23+
private VersionType versionType;
2224

23-
VersionType versionType;
25+
private List<VersionFile> files;
2426

25-
List<VersionFile> files;
27+
private List<VersionDependency> dependencies;
2628

27-
List<VersionDependency> dependencies;
29+
private List<String> gameVersions;
2830

29-
List<String> gameVersions;
31+
private List<String> loaders;
3032
}

0 commit comments

Comments
 (0)