libmodpm
Version:
Modrinth package manager library
291 lines • 19.6 kB
JavaScript
// SPDX-License-Identifier: GPL-3.0-or-later
import { HTTPClient } from "../HTTPClient.js";
/**
* Represents an error returned by the registry API.
*
* @final
*/
class RegistryError extends Error {
/**
* Error code.
*/
code;
/**
* Creates a new registry error.
*
* @param description Error description.
* @param [code] Error code.
*/
constructor(description, code) {
super(description);
this.name = new.target.name;
this.code = code;
}
}
/**
* Provides methods for interacting with the registry via its HTTP API.
*
* @final
*/
export class RegistryClient extends HTTPClient {
static RegistryError = RegistryError;
/**
* Base URL for HTTP requests.
*/
baseUrl;
/**
* Creates a new registry client.
*
* @param userAgent User agent string used when making requests to the registry.
* @param [token] API authentication token.
* @param [baseUrl=new URL("https://api.modrinth.com/v2/")] API authentication token. Requires the following scopes:
* - `PROJECT_READ`
* - `VERSION_READ`
*
* Authentication is only needed for accessing private/draft packages and their versions.
*/
constructor(userAgent, token, baseUrl = new URL("https://api.modrinth.com/v2/")) {
super(userAgent, token);
this.baseUrl = baseUrl;
}
/**
* Catches 404 errors and returns null.
*
* @param error Error to try to catch.
* @returns `null` if the error is a 404 error.
* @throws {@link RegistryClient.RegistryError} If the error is not a 404 error.
*/
static catch404(error) {
if (error instanceof RegistryClient.RegistryError && error.message === "404")
return null;
throw error;
}
/**
* Retrieves the package associated with the specified ID.
*
* @param id Package ID.
* @returns `null` if the package is not found.
* @throws {@link RegistryClient.RegistryError} If the request fails.
* @throws {@link !TypeError} If fetching fails.
*/
async getPackage(id) {
return this.fetch(["project", id])
.then(res => res.json())
.catch(RegistryClient.catch404);
}
/**
* Retrieves the ID of the package associated with the specified slug.
*
* This method returns the ID even for private/draft packages, without requiring authentication.
*
* @param slug Package slug.
* @returns `null` if the package is not found.
* @throws {@link RegistryClient.RegistryError} If the request fails.
* @throws {@link !TypeError} If fetching fails.
*/
async getPackageId(slug) {
return this.fetch(["project", slug, "check"])
.then(res => res.json())
.then(json => json.id)
.catch(RegistryClient.catch404);
}
/**
* Retrieves packages matching the specified search query and facet filters.
*
* @param [query] Search query.
* @param [facets] Facets to filter by.
* @param [sort] Sort order.
* @param [offset] Offset into the search.
* @param [limit] Maximum number of results to return.
*
* @see https://docs.modrinth.com/api/operations/searchprojects/ Search projects | Modrinth Documentation
*/
async search(query, facets, sort, offset, limit = 20) {
const queryParams = new URLSearchParams();
if (query !== undefined)
queryParams.set("query", query);
if (facets !== undefined)
queryParams.set("facets", JSON.stringify(facets));
if (sort !== undefined)
queryParams.set("sort", sort);
if (offset !== undefined)
queryParams.set("offset", offset.toString());
queryParams.set("limit", limit.toString());
const body = await this.fetch(["search"], {}, queryParams).then(res => res.text());
return JSON.parse(body, (_, value) => {
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(value))
return new Date(value);
return value;
});
}
/**
* Retrieves the version associated with the specified version ID.
*
* @param id Version ID.
* @returns `null` if the version is not found.
* @throws {@link RegistryClient.RegistryError} If the request fails.
* @throws {@link !TypeError} If fetching fails.
*/
async getVersion(id) {
return this.fetch(["version", id])
.then(res => res.json())
.catch(RegistryClient.catch404);
}
/**
* Retrieves the versions associated with the specified package.
*
* Filters:
* - `loaders` — restricts versions to those compatible with the specified loaders.
* - `game_versions` — restricts versions to those compatible with the specified game versions.
* - `version_type` — restricts versions to those of the specified type (release channel).
*
* @param pkg Package ID.
* @param [filters] Filters to apply.
* @returns List of matching versions, sorted in descending order, with the latest version first.
* @throws {@link RegistryClient.RegistryError} If the request fails.
* @throws {@link !TypeError} If fetching fails.
*/
async listVersions(pkg, filters = {}) {
const params = new URLSearchParams();
if (filters.loaders !== undefined)
params.append("loaders", JSON.stringify(filters.loaders));
if (filters.game_versions !== undefined)
params.append("game_versions", JSON.stringify(filters.game_versions));
if (filters.version_type !== undefined)
params.append("version_type", JSON.stringify(filters.version_type));
return this.fetch(["project", pkg, "version"], {}, params)
.then(res => res.json())
.catch(RegistryClient.catch404);
}
/**
* Retrieves the packages associated with the specified IDs.
*
* @param ids Package IDs.
* @throws {@link RegistryClient.RegistryError} If the request fails.
* @throws {@link !TypeError} If fetching fails.
*/
async getPackages(ids) {
return this.fetch(["projects"], {}, new URLSearchParams({
ids: JSON.stringify(ids),
})).then(res => res.json());
}
/**
* Retrieves the version associated with the specified file hash.
*
* @param hash SHA-512 hash of the file, encoded as a hex string.
* @returns `null` if the version is not found.
* @throws {@link RegistryClient.RegistryError} If the request fails.
* @throws {@link !TypeError} If fetching fails.
*/
async getVersionByHash(hash) {
return this.fetch(["version_file", hash], {}, new URLSearchParams({
algorithm: "sha512",
}))
.then(res => res.json())
.catch(RegistryClient.catch404);
}
/**
* Retrieves the versions associated with the specified hashes.
*
* @param hashes SHA-512 hashes of the files, encoded as a hex strings.
* @throws {@link RegistryClient.RegistryError} If the request fails.
* @throws {@link !TypeError} If fetching fails.
*/
async getVersionsByHashes(hashes) {
return this.fetch(["version_files"], {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
hashes,
algorithm: "sha512",
}),
}).then(res => res.json())
.then((json) => Object.values(json));
}
/**
* Retrieves the versions associated with the specified version IDs.
*
* @param ids Version IDs.
* @throws {@link RegistryClient.RegistryError} If the request fails.
* @throws {@link !TypeError} If fetching fails.
*/
async getVersions(ids) {
return this.fetch(["versions"], {}, new URLSearchParams({
ids: JSON.stringify(ids),
})).then(res => res.json());
}
/**
* Retrieves the version associated with the specified package and version number or ID.
*
* @param pkg Package ID.
* @param version Version number or ID.
* @returns `null` if the package or version is not found.
* @throws {@link RegistryClient.RegistryError} If the request fails.
* @throws {@link !TypeError} If fetching fails.
*/
async getVersionByNumber(pkg, version) {
return this.fetch(["project", pkg, "version", version])
.then(res => res.json())
.catch(err => {
if (err instanceof RegistryClient.RegistryError && err.message === "404")
return null;
throw err;
});
}
/**
* Retrieves the latest versions associated with the specified hashes.
*
* Filters:
* - `loaders` — restricts versions to those compatible with the specified loaders.
* - `game_versions` — restricts versions to those compatible with the specified game versions.
* - `version_type` — restricts versions to those of the specified type (release channel).
*
* @param hashes SHA-512 hashes of the files, encoded as a hex strings.
* @param [filters] Filters to apply.
* @throws {@link RegistryClient.RegistryError} If the request fails.
* @throws {@link !TypeError} If fetching fails.
*/
async getLatestVersions(hashes, filters = {}) {
return this.fetch(["version_files", "update"], {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
hashes,
algorithm: "sha512",
loaders: filters.loaders,
game_versions: filters.game_versions,
version_type: filters.version_type,
}),
}).then(res => res.json());
}
/**
* Retrieves all loaders supported by the registry.
*
* @throws {@link RegistryClient.RegistryError} If the request fails.
* @throws {@link !TypeError} If fetching fails.
*/
async getLoaders() {
return this.fetch(["tag", "loader"])
.then(res => res.json())
.then((json) => json.map((l) => l.name));
}
async createError(res) {
if ((res.headers.get("Content-Type")?.startsWith("application/json") ?? false)
&& res.body !== null) {
const { description, error } = await res.json();
return new RegistryError(description, error);
}
return new RegistryError(res.status.toString());
}
// only adding an overload… sorry @final 😔
async fetch(url, options, query, retries = 3, remainingRetries = retries) {
if (Array.isArray(url))
return super.fetch(new URL(url.map(globalThis.encodeURIComponent).join("/"), this.baseUrl), options, query, remainingRetries);
return super.fetch(url, options, query, remainingRetries);
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUmVnaXN0cnlDbGllbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvcmVnaXN0cnkvUmVnaXN0cnlDbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsNENBQTRDO0FBQzVDLE9BQU8sRUFBQyxVQUFVLEVBQUMsTUFBTSxrQkFBa0IsQ0FBQztBQVE1Qzs7OztHQUlHO0FBQ0gsTUFBTSxhQUFjLFNBQVEsS0FBSztJQUM3Qjs7T0FFRztJQUNhLElBQUksQ0FBVTtJQUU5Qjs7Ozs7T0FLRztJQUNILFlBQW1CLFdBQW1CLEVBQUUsSUFBYTtRQUNqRCxLQUFLLENBQUMsV0FBVyxDQUFDLENBQUM7UUFDbkIsSUFBSSxDQUFDLElBQUksR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQztRQUM1QixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztJQUNyQixDQUFDO0NBQ0o7QUFFRDs7OztHQUlHO0FBQ0gsTUFBTSxPQUFPLGNBQWUsU0FBUSxVQUF5QjtJQUNsRCxNQUFNLENBQVUsYUFBYSxHQUFHLGFBQWEsQ0FBQztJQUVyRDs7T0FFRztJQUNjLE9BQU8sQ0FBTTtJQUU5Qjs7Ozs7Ozs7OztPQVVHO0lBQ0gsWUFBbUIsU0FBaUIsRUFBRSxLQUFjLEVBQUUsT0FBTyxHQUFHLElBQUksR0FBRyxDQUFDLDhCQUE4QixDQUFDO1FBQ25HLEtBQUssQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDeEIsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7SUFDM0IsQ0FBQztJQUVEOzs7Ozs7T0FNRztJQUNJLE1BQU0sQ0FBQyxRQUFRLENBQUMsS0FBWTtRQUMvQixJQUFJLEtBQUssWUFBWSxjQUFjLENBQUMsYUFBYSxJQUFJLEtBQUssQ0FBQyxPQUFPLEtBQUssS0FBSztZQUN4RSxPQUFPLElBQUksQ0FBQztRQUNoQixNQUFNLEtBQUssQ0FBQztJQUNoQixDQUFDO0lBRUQ7Ozs7Ozs7T0FPRztJQUNJ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