mirror of
https://github.com/Kir-Antipov/mc-publish.git
synced 2024-11-22 00:11:02 -05:00
Moved curseforge
module
This commit is contained in:
parent
9d0bdf20b7
commit
19fc58d31e
2 changed files with 66 additions and 153 deletions
66
src/platforms/curseforge/index.ts
Normal file
66
src/platforms/curseforge/index.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
export {
|
||||
CurseForgeUploadApiClient,
|
||||
CurseForgeUploadApiOptions,
|
||||
|
||||
CURSEFORGE_UPLOAD_API_URL,
|
||||
} from "./curseforge-upload-api-client";
|
||||
|
||||
export {
|
||||
CurseForgeEternalApiClient,
|
||||
CurseForgeEternalApiOptions,
|
||||
|
||||
CURSEFORGE_ETERNAL_API_URL,
|
||||
} from "./curseforge-eternal-api-client";
|
||||
|
||||
export {
|
||||
CurseForgeDependency,
|
||||
} from "./curseforge-dependency";
|
||||
|
||||
export {
|
||||
CurseForgeDependencyType,
|
||||
} from "./curseforge-dependency-type";
|
||||
|
||||
export {
|
||||
CurseForgeGameVersion,
|
||||
|
||||
CURSEFORGE_GAME_VERSION_PLUGIN_NAME_COMPARER,
|
||||
CURSEFORGE_GAME_VERSION_SNAPSHOT_NAME_COMPARER,
|
||||
|
||||
findCurseForgeGameVersionIdsByNames,
|
||||
} from "./curseforge-game-version";
|
||||
|
||||
export {
|
||||
CurseForgeGameVersionMap,
|
||||
|
||||
createCurseForgeGameVersionMap,
|
||||
} from "./curseforge-game-version-map";
|
||||
|
||||
export {
|
||||
CurseForgeGameVersionType,
|
||||
} from "./curseforge-game-version-type";
|
||||
|
||||
export {
|
||||
CurseForgeGameVersionUnion,
|
||||
} from "./curseforge-game-version-union";
|
||||
|
||||
export {
|
||||
CurseForgeProject,
|
||||
|
||||
isCurseForgeProjectId,
|
||||
} from "./curseforge-project";
|
||||
|
||||
export {
|
||||
CurseForgeUploader,
|
||||
CurseForgeUploaderOptions,
|
||||
CurseForgeUploadRequest,
|
||||
CurseForgeUploadReport,
|
||||
} from "./curseforge-uploader";
|
||||
|
||||
export {
|
||||
CurseForgeVersion,
|
||||
CurseForgeVersionInit,
|
||||
} from "./curseforge-version";
|
||||
|
||||
export {
|
||||
CurseForgeFile,
|
||||
} from "./curseforge-file";
|
|
@ -1,153 +0,0 @@
|
|||
import fetch from "node-fetch";
|
||||
import FormData from "form-data";
|
||||
import File from "../io/file";
|
||||
import { findVersionByName } from "../minecraft";
|
||||
import SoftError from "../soft-error";
|
||||
|
||||
const baseUrl = "https://minecraft.curseforge.com/api";
|
||||
|
||||
interface CurseForgeVersion {
|
||||
id: number;
|
||||
gameVersionTypeID: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface CurseForgeVersions {
|
||||
gameVersions: CurseForgeVersion[];
|
||||
loaders: CurseForgeVersion[];
|
||||
java: CurseForgeVersion[];
|
||||
}
|
||||
|
||||
interface CurseForgeUploadErrorInfo {
|
||||
errorCode: number;
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
class CurseForgeUploadError extends SoftError {
|
||||
public readonly info?: CurseForgeUploadErrorInfo;
|
||||
|
||||
constructor(soft: boolean, message?: string, info?: CurseForgeUploadErrorInfo) {
|
||||
super(soft, message);
|
||||
this.info = info;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchJsonArray<T>(url: string): Promise<T[] | never> {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
const isSoft = response.status === 429 || response.status >= 500;
|
||||
throw new SoftError(isSoft, `${response.status} (${response.statusText})`);
|
||||
}
|
||||
|
||||
let array: T[];
|
||||
try {
|
||||
array = await response.json();
|
||||
} catch {
|
||||
array = null;
|
||||
}
|
||||
|
||||
if (!Array.isArray(array)) {
|
||||
throw new SoftError(true, "CurseForge sometimes returns Cloudflare's HTML page instead of its API response. Yeah, I know, very cool. Just wait 15-20 minutes, then try re-running this action, and you should be fine.");
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
let cachedCurseForgeVersions: CurseForgeVersions = null;
|
||||
async function getCurseForgeVersions(token: string): Promise<CurseForgeVersions> {
|
||||
if (!cachedCurseForgeVersions) {
|
||||
cachedCurseForgeVersions = await loadCurseForgeVersions(token);
|
||||
}
|
||||
return cachedCurseForgeVersions;
|
||||
}
|
||||
|
||||
async function loadCurseForgeVersions(token: string): Promise<CurseForgeVersions> {
|
||||
const versionTypes = await fetchJsonArray<{ id: number, slug: string }>(`${baseUrl}/game/version-types?token=${token}`);
|
||||
const javaVersionTypes = versionTypes.filter(x => x.slug.startsWith("java")).map(x => x.id);
|
||||
const minecraftVersionTypes = versionTypes.filter(x => x.slug.startsWith("minecraft")).map(x => x.id);
|
||||
const loaderVersionTypes = versionTypes.filter(x => x.slug.startsWith("modloader")).map(x => x.id);
|
||||
|
||||
const versions = await fetchJsonArray<CurseForgeVersion>(`${baseUrl}/game/versions?token=${token}`);
|
||||
return versions.reduce((container, version) => {
|
||||
if (javaVersionTypes.includes(version.gameVersionTypeID)) {
|
||||
container.java.push(version);
|
||||
} else if (minecraftVersionTypes.includes(version.gameVersionTypeID)) {
|
||||
container.gameVersions.push(version);
|
||||
} else if (loaderVersionTypes.includes(version.gameVersionTypeID)) {
|
||||
container.loaders.push(version);
|
||||
}
|
||||
return container;
|
||||
}, { gameVersions: new Array<CurseForgeVersion>(), loaders: new Array<CurseForgeVersion>(), java: new Array<CurseForgeVersion>() });
|
||||
}
|
||||
|
||||
export async function unifyGameVersion(gameVersion: string): Promise<string> {
|
||||
gameVersion = gameVersion.trim();
|
||||
const minecraftVersion = await findVersionByName(gameVersion);
|
||||
if (minecraftVersion) {
|
||||
return `${minecraftVersion.name}${(minecraftVersion.isSnapshot ? "-Snapshot" : "")}`;
|
||||
}
|
||||
return gameVersion.replace(/([^\w]|_)+/g, ".").replace(/[.-][a-zA-Z]\w+$/, "-Snapshot");
|
||||
}
|
||||
|
||||
export function unifyJava(java: string): string {
|
||||
java = java.trim();
|
||||
const match = java.match(/(?:\d+\D)?(\d+)$/);
|
||||
if (match && match.length === 2) {
|
||||
return `Java ${match[1]}`;
|
||||
}
|
||||
return java;
|
||||
}
|
||||
|
||||
async function addVersionIntersectionToSet(curseForgeVersions: CurseForgeVersion[], versions: string[], unify: (v: string) => string | Promise<string>, comparer: (cfv: CurseForgeVersion, v: string) => boolean, intersection: Set<number> ) {
|
||||
for (const version of versions) {
|
||||
const unifiedVersion = await unify(version);
|
||||
const curseForgeVersion = curseForgeVersions.find(x => comparer(x, unifiedVersion));
|
||||
if (curseForgeVersion) {
|
||||
intersection.add(curseForgeVersion.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function convertToCurseForgeVersions(gameVersions: string[], loaders: string[], java: string[], token: string): Promise<number[]> {
|
||||
const versions = new Set<number>();
|
||||
const curseForgeVersions = await getCurseForgeVersions(token);
|
||||
|
||||
await addVersionIntersectionToSet(curseForgeVersions.gameVersions, gameVersions, unifyGameVersion, (cfv, v) => cfv.name === v, versions);
|
||||
await addVersionIntersectionToSet(curseForgeVersions.loaders, loaders, x => x.trim().toLowerCase(), (cfv, v) => cfv.slug === v, versions);
|
||||
await addVersionIntersectionToSet(curseForgeVersions.java, java, unifyJava, (cfv, v) => cfv.name === v, versions);
|
||||
|
||||
return [...versions];
|
||||
}
|
||||
|
||||
export async function uploadFile(id: string, data: Record<string, any>, file: File, token: string): Promise<number> {
|
||||
if (Array.isArray(data.relations?.projects) && (!data.relations.projects.length || data.parentFileID)) {
|
||||
delete data.relations;
|
||||
}
|
||||
|
||||
if (data.gameVersions && data.parentFileID) {
|
||||
delete data.gameVersions;
|
||||
}
|
||||
|
||||
const form = new FormData();
|
||||
form.append("file", file.getStream(), file.name);
|
||||
form.append("metadata", JSON.stringify(data));
|
||||
|
||||
const response = await fetch(`${baseUrl}/projects/${id}/upload-file?token=${token}`, {
|
||||
method: "POST",
|
||||
headers: form.getHeaders(),
|
||||
body: <any>form
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
let errorText = response.statusText;
|
||||
let info: CurseForgeUploadErrorInfo;
|
||||
try {
|
||||
info = <CurseForgeUploadErrorInfo>await response.json();
|
||||
errorText += `, ${JSON.stringify(info)}`;
|
||||
} catch { }
|
||||
const isSoftError = response.status === 429 || response.status >= 500;
|
||||
throw new CurseForgeUploadError(isSoftError, `Failed to upload file: ${response.status} (${errorText})`, info);
|
||||
}
|
||||
|
||||
return (<{ id: number }>await response.json()).id;
|
||||
}
|
Loading…
Reference in a new issue