mirror of
https://github.com/Kir-Antipov/mc-publish.git
synced 2024-11-21 16:00:59 -05:00
Refactored ModrinthPublisher
(-> ModrinthUploader
)
This commit is contained in:
parent
a90d0659d7
commit
2c627b694a
2 changed files with 193 additions and 100 deletions
193
src/platforms/modrinth/modrinth-uploader.ts
Normal file
193
src/platforms/modrinth/modrinth-uploader.ts
Normal file
|
@ -0,0 +1,193 @@
|
|||
import { ModrinthUploadReport as UploadReport, ModrinthUploadRequest as UploadRequest } from "@/action";
|
||||
import { Dependency } from "@/dependencies";
|
||||
import { GenericPlatformUploader, GenericPlatformUploaderOptions } from "@/platforms/generic-platform-uploader";
|
||||
import { PlatformType } from "@/platforms/platform-type";
|
||||
import { $i } from "@/utils/collections";
|
||||
import { IGNORE_CASE_AND_NON_WORD_CHARACTERS_EQUALITY_COMPARER } from "@/utils/comparison";
|
||||
import { ModrinthApiClient } from "./modrinth-api-client";
|
||||
import { ModrinthDependency } from "./modrinth-dependency";
|
||||
import { ModrinthDependencyType } from "./modrinth-dependency-type";
|
||||
import { ModrinthProject } from "./modrinth-project";
|
||||
import { ModrinthUnfeatureMode } from "./modrinth-unfeature-mode";
|
||||
import { ModrinthVersion } from "./modrinth-version";
|
||||
|
||||
/**
|
||||
* Configuration options for the uploader, tailored for use with Modrinth.
|
||||
*/
|
||||
export type ModrinthUploaderOptions = GenericPlatformUploaderOptions;
|
||||
|
||||
/**
|
||||
* Defines the structure for an upload request, adapted for use with Modrinth.
|
||||
*/
|
||||
export type ModrinthUploadRequest = UploadRequest;
|
||||
|
||||
/**
|
||||
* Specifies the structure of the report generated after a successful upload to Modrinth.
|
||||
*/
|
||||
export type ModrinthUploadReport = UploadReport;
|
||||
|
||||
/**
|
||||
* Implements the uploader for Modrinth.
|
||||
*/
|
||||
export class ModrinthUploader extends GenericPlatformUploader<ModrinthUploaderOptions, ModrinthUploadRequest, ModrinthUploadReport> {
|
||||
/**
|
||||
* Constructs a new {@link ModrinthUploader} instance.
|
||||
*
|
||||
* @param options - The options to use for the uploader.
|
||||
*/
|
||||
constructor(options?: ModrinthUploaderOptions) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
get platform(): PlatformType {
|
||||
return PlatformType.MODRINTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected async uploadCore(request: ModrinthUploadRequest): Promise<ModrinthUploadReport> {
|
||||
const api = new ModrinthApiClient({ token: request.token.unwrap() });
|
||||
|
||||
const project = await this.getProject(request.id, api);
|
||||
const version = await this.createVersion(request, project, api);
|
||||
await this.unfeaturePreviousVersions(version, request.unfeatureMode, api);
|
||||
|
||||
return {
|
||||
id: project.id,
|
||||
version: version.id,
|
||||
url: `https://modrinth.com/${project.project_type}/${project.slug}/version/${version.name}`,
|
||||
files: version.files.map(x => ({ id: x.hashes.sha1, name: x.filename, url: x.url })),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the project details from Modrinth.
|
||||
*
|
||||
* @param idOrSlug - The identifier or slug of the project.
|
||||
* @param api - The API client instance to use for the request.
|
||||
*
|
||||
* @returns The fetched project details.
|
||||
*/
|
||||
private async getProject(idOrSlug: string, api: ModrinthApiClient): Promise<ModrinthProject> {
|
||||
const project = await api.getProject(idOrSlug);
|
||||
if (!project) {
|
||||
throw new Error(`Modrinth project "${idOrSlug}" was not found.`);
|
||||
}
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new version of the project on Modrinth.
|
||||
*
|
||||
* @param request - The upload request containing information about the new version.
|
||||
* @param project - The project for which the new version is created.
|
||||
* @param api - The API client instance to use for the upload request.
|
||||
*
|
||||
* @returns The details of the newly created version.
|
||||
*/
|
||||
private async createVersion(request: ModrinthUploadRequest, project: ModrinthProject, api: ModrinthApiClient): Promise<ModrinthVersion> {
|
||||
const gameVersions = await this.convertToModrinthGameVersionNames(request.gameVersions, api);
|
||||
const loaders = await this.convertToModrinthLoaderNames(request.loaders, project, api);
|
||||
const dependencies = await this.convertToModrinthDependencies(request.dependencies, api);
|
||||
|
||||
return await api.createVersion({
|
||||
project_id: project.id,
|
||||
name: request.name,
|
||||
version_number: request.version,
|
||||
changelog: request.changelog,
|
||||
version_type: request.versionType,
|
||||
featured: request.featured,
|
||||
game_versions: gameVersions,
|
||||
loaders,
|
||||
dependencies,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the dependencies to Modrinth-specific format.
|
||||
*
|
||||
* @param dependencies - The list of dependencies to convert.
|
||||
* @param api - The API client instance to use for retrieving data.
|
||||
*
|
||||
* @returns An array of converted dependencies.
|
||||
*/
|
||||
private async convertToModrinthDependencies(dependencies: Dependency[], api: ModrinthApiClient): Promise<ModrinthDependency[]> {
|
||||
const simpleDependencies = this.convertToSimpleDependencies(dependencies, ModrinthDependencyType.fromDependencyType);
|
||||
const modrinthDependencies = await Promise.all(simpleDependencies.map(async ([id, type]) => ({
|
||||
project_id: await api.getProjectId(id).catch(() => undefined as string),
|
||||
dependency_type: type,
|
||||
})));
|
||||
return modrinthDependencies.filter(x => x.project_id && x.dependency_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts loader names to Modrinth-specific format.
|
||||
*
|
||||
* @param loaders - The list of loaders to convert.
|
||||
* @param project - The project for which the loaders are used.
|
||||
* @param api - The API client instance to use for retrieving data.
|
||||
*
|
||||
* @returns An array of converted loader names.
|
||||
*/
|
||||
private async convertToModrinthLoaderNames(loaders: string[], project: ModrinthProject, api: ModrinthApiClient): Promise<string[]> {
|
||||
if (!loaders?.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const modrinthLoaders = await api.getLoaders();
|
||||
return $i(loaders)
|
||||
.map(x => modrinthLoaders.find(y => IGNORE_CASE_AND_NON_WORD_CHARACTERS_EQUALITY_COMPARER(x, y.name)))
|
||||
.filter(x => x.supported_project_types?.includes(project.project_type))
|
||||
.map(x => x.name)
|
||||
.toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts game version names to Modrinth-specific format.
|
||||
*
|
||||
* @param gameVersions - The list of game versions to convert.
|
||||
* @param api - The API client instance to use for retrieving data.
|
||||
*
|
||||
* @returns An array of converted game version names.
|
||||
*/
|
||||
private async convertToModrinthGameVersionNames(gameVersions: string[], api: ModrinthApiClient): Promise<string[]> {
|
||||
if (!gameVersions?.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const modrinthGameVersions = await api.getGameVersions();
|
||||
return $i(gameVersions)
|
||||
.map(x => modrinthGameVersions.find(y => IGNORE_CASE_AND_NON_WORD_CHARACTERS_EQUALITY_COMPARER(x, y.version))?.version)
|
||||
.filter(x => x)
|
||||
.toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfeatures previous versions of the project on Modrinth.
|
||||
*
|
||||
* @param version - The new version after which the previous ones should be unfeatured.
|
||||
* @param unfeatureMode - The mode to determine which versions should be unfeatured.
|
||||
* @param api - The API client instance to use for the unfeaturing request.
|
||||
*/
|
||||
private async unfeaturePreviousVersions(version: ModrinthVersion, unfeatureMode: ModrinthUnfeatureMode, api: ModrinthApiClient): Promise<void> {
|
||||
if (unfeatureMode === ModrinthUnfeatureMode.NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._logger.info(`🔽 Initiating unfeaturing of older Modrinth project versions`);
|
||||
const result = await api.unfeaturePreviousProjectVersions(version, unfeatureMode);
|
||||
const unfeaturedVersions = Object.entries(result).filter(([, success]) => success).map(([version]) => version);
|
||||
const nonUnfeaturedVersions = Object.entries(result).filter(([, success]) => !success).map(([version]) => version);
|
||||
if (unfeaturedVersions.length) {
|
||||
this._logger.info(`🟢 Successfully unfeatured ${unfeaturedVersions.join(", ")}`);
|
||||
}
|
||||
if (nonUnfeaturedVersions.length) {
|
||||
this._logger.info(`⚠️ Failed to unfeature ${nonUnfeaturedVersions.join(", ")}. Please, double-check your token`);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
import { createVersion, getProject, getVersions, modifyVersion } from "../../utils/modrinth";
|
||||
import File from "../../utils/io/file";
|
||||
import ModPublisher from "../mod-publisher";
|
||||
import PublisherTarget from "../publisher-target";
|
||||
import Dependency from "../../metadata/dependency";
|
||||
import DependencyKind from "../../metadata/dependency-kind";
|
||||
import { mapBooleanInput, mapEnumInput } from "../../utils/actions/input";
|
||||
import LoggingStopwatch from "../../utils/logging/logging-stopwatch";
|
||||
|
||||
enum UnfeatureMode {
|
||||
None = 0,
|
||||
|
||||
VersionSubset = 1,
|
||||
VersionIntersection = 2,
|
||||
VersionAny = 4,
|
||||
|
||||
LoaderSubset = 8,
|
||||
LoaderIntersection = 16,
|
||||
LoaderAny = 32,
|
||||
|
||||
Subset = VersionSubset | LoaderSubset,
|
||||
Intersection = VersionIntersection | LoaderIntersection,
|
||||
Any = VersionAny | LoaderAny,
|
||||
}
|
||||
|
||||
function hasFlag(unfeatureMode: UnfeatureMode, flag: UnfeatureMode): boolean {
|
||||
return (unfeatureMode & flag) === flag;
|
||||
}
|
||||
|
||||
const modrinthDependencyKinds = new Map([
|
||||
[DependencyKind.Depends, "required"],
|
||||
[DependencyKind.Recommends, "optional"],
|
||||
[DependencyKind.Suggests, "optional"],
|
||||
[DependencyKind.Includes, "embedded"],
|
||||
[DependencyKind.Breaks, "incompatible"],
|
||||
]);
|
||||
|
||||
export default class ModrinthPublisher extends ModPublisher {
|
||||
public get target(): PublisherTarget {
|
||||
return PublisherTarget.Modrinth;
|
||||
}
|
||||
|
||||
protected async publishMod(id: string, token: string, name: string, version: string, channel: string, loaders: string[], gameVersions: string[], _java: string[], changelog: string, files: File[], dependencies: Dependency[], options: Record<string, unknown>): Promise<void> {
|
||||
const featured = mapBooleanInput(options.featured, true);
|
||||
const unfeatureMode = mapEnumInput(options.unfeatureMode, UnfeatureMode, featured ? UnfeatureMode.Subset : UnfeatureMode.None);
|
||||
const projects = (await Promise.all(dependencies
|
||||
.filter((x, _, self) => (x.kind !== DependencyKind.Suggests && x.kind !== DependencyKind.Includes) || !self.find(y => y.id === x.id && y.kind !== DependencyKind.Suggests && y.kind !== DependencyKind.Includes))
|
||||
.map(async x => ({
|
||||
project_id: (await getProject(x.getProjectSlug(this.target)))?.id,
|
||||
dependency_type: modrinthDependencyKinds.get(x.kind)
|
||||
}))))
|
||||
.filter(x => x.project_id && x.dependency_type);
|
||||
|
||||
if (unfeatureMode !== UnfeatureMode.None) {
|
||||
await this.unfeatureOlderVersions(id, token, unfeatureMode, loaders, gameVersions);
|
||||
}
|
||||
|
||||
const data = {
|
||||
name: name || version,
|
||||
version_number: version,
|
||||
changelog,
|
||||
game_versions: gameVersions,
|
||||
version_type: channel,
|
||||
loaders,
|
||||
featured,
|
||||
dependencies: projects
|
||||
};
|
||||
await createVersion(id, data, files, token);
|
||||
}
|
||||
|
||||
private async unfeatureOlderVersions(id: string, token: string, unfeatureMode: UnfeatureMode, loaders: string[], gameVersions: string[]): Promise<void> {
|
||||
const unfeaturedVersions = new Array<string>();
|
||||
const stopwatch = LoggingStopwatch.startNew(this.logger, "📝 Unfeaturing older Modrinth versions...", ms => `✅ Successfully unfeatured: ${unfeaturedVersions.join(", ")} (in ${ms} ms)`);
|
||||
|
||||
const versionSubset = hasFlag(unfeatureMode, UnfeatureMode.VersionSubset);
|
||||
const loaderSubset = hasFlag(unfeatureMode, UnfeatureMode.LoaderSubset);
|
||||
const olderVersions = await getVersions(id, hasFlag(unfeatureMode, UnfeatureMode.LoaderAny) ? null : loaders, hasFlag(unfeatureMode, UnfeatureMode.VersionAny) ? null : gameVersions, true, token);
|
||||
for (const olderVersion of olderVersions) {
|
||||
if (loaderSubset && !olderVersion.loaders.every(x => loaders.includes(x))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (versionSubset && !olderVersion.game_versions.every(x => gameVersions.includes(x))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (await modifyVersion(olderVersion.id, { featured: false }, token)) {
|
||||
unfeaturedVersions.push(olderVersion.id);
|
||||
} else {
|
||||
this.logger.warn(`⚠️ Cannot unfeature version ${olderVersion.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (unfeaturedVersions.length) {
|
||||
stopwatch.stop();
|
||||
} else {
|
||||
this.logger.info("✅ No versions to unfeature were found");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue