Added Cursefoge Source

This commit is contained in:
Kallum Jones 2022-08-09 14:55:54 +01:00
parent fdc880e7fc
commit 7d6d49ec19
No known key found for this signature in database
GPG Key ID: D7F4589C4D7F81A9
4 changed files with 116 additions and 30 deletions

View File

@ -7,14 +7,22 @@ import ModManager from "../mod-manager.js";
export default class FileDownloader { export default class FileDownloader {
static downloadMod(version: Version): void { static downloadMod(version: Version): void {
https.get(version.url, res => { try {
const filePath = path.join(ModManager.FilePaths.MODS_FOLDER_PATH, version.fileName); if (version.url == null) {
const writeStream = createWriteStream(filePath); throw new Error("URL was null");
res.pipe(writeStream); }
writeStream.on("finish", () => writeStream.close());
writeStream.on('error', () => { https.get(version.url, res => {
throw new DownloadError(`Failed to download ${version.fileName} from ${version.url}`) const filePath = path.join(ModManager.FilePaths.MODS_FOLDER_PATH, version.fileName);
const writeStream = createWriteStream(filePath);
res.pipe(writeStream);
writeStream.on("finish", () => writeStream.close());
writeStream.on('error', () => {
throw new Error("Error while writing file during download")
})
}) })
}) } catch (e) {
throw new DownloadError(`Failed to download ${version.fileName} from ${version.url}`)
}
} }
} }

View File

@ -16,6 +16,7 @@ import MigratePossibleCommand from "./commands/migrate_possible.js";
import MigrateCommand from "./commands/migrate_command.js"; import MigrateCommand from "./commands/migrate_command.js";
import ModrinthSource from "./mods/sources/modrinth_source.js"; import ModrinthSource from "./mods/sources/modrinth_source.js";
import Mods from "./mods/mods.js"; import Mods from "./mods/mods.js";
import {CurseforgeSource} from "./mods/sources/curseforge_source.js";
export default class ModManager { export default class ModManager {
public static logger: Logger | null = null; public static logger: Logger | null = null;
@ -42,7 +43,7 @@ export default class ModManager {
public static readonly MODS_FOLDER_PATH = path.join("mods") public static readonly MODS_FOLDER_PATH = path.join("mods")
} }
static async init() { static init() {
if (Initialiser.isInitialised()) { if (Initialiser.isInitialised()) {
this.logger = ModManager.createLogger(); this.logger = ModManager.createLogger();
} }
@ -55,11 +56,8 @@ export default class ModManager {
command.registerCommand(this.program); command.registerCommand(this.program);
} }
/* const source = new CurseforgeSource();
console.log(await source.search("lithium"))*/
Mods.registerSource(new ModrinthSource()) Mods.registerSource(new ModrinthSource())
//Mods.registerSource(new CurseforgeSource(), "CURSEFORGE_API_KEY") Mods.registerSource(new CurseforgeSource(), "CURSEFORGE_API_KEY")
this.program.showSuggestionAfterError(); this.program.showSuggestionAfterError();
this.program.showHelpAfterError(); this.program.showHelpAfterError();

View File

@ -37,7 +37,7 @@ export default class Mods {
id = await source.search(mod); id = await source.search(mod);
} catch (e) { } catch (e) {
if (e instanceof ModNotFoundError) { if (e instanceof ModNotFoundError) {
spinner.stop(`Mod not found on ${source.getSourceName()}`) spinner.stop(`Mod ${mod} not found on ${source.getSourceName()}`)
} else { } else {
spinner.error(`An error occurred searching for ${mod} on ${source.getSourceName()}. Skipping ${source.getSourceName()}`) spinner.error(`An error occurred searching for ${mod} on ${source.getSourceName()}. Skipping ${source.getSourceName()}`)
// Try the next source // Try the next source

View File

@ -1,31 +1,95 @@
/*import ModSource from "./mod_source.js"; import ModSource from "./mod_source.js";
import axios from "axios"; import axios from "axios";
import MinecraftUtils from "../../util/minecraft_utils.js"; import MinecraftUtils from "../../util/minecraft_utils.js";
import ModNotFoundError from "../../errors/mod_not_found_error.js"; import ModNotFoundError from "../../errors/mod_not_found_error.js";
import Util from "../../util/util.js";
import {format} from "util";
import Mods from "../mods.js";
import FileDownloader from "../../io/file_downloder.js";
import DownloadError from "../../errors/download_error.js";
export class CurseforgeSource implements ModSource { export class CurseforgeSource implements ModSource {
private static readonly BASE_URL: string = "https://api.curseforge.com"; private static readonly BASE_URL: string = "https://api.curseforge.com/v1";
private static readonly SEARCH_URL: string = `${CurseforgeSource.BASE_URL}/v1/mods/search`; private static readonly SEARCH_URL: string = `${CurseforgeSource.BASE_URL}/mods/search`;
private static readonly GET_MOD_URL: string = `${CurseforgeSource.BASE_URL}/mods/%s`
private static readonly GET_FILE_URL: string = `${CurseforgeSource.BASE_URL}/mods/%s/files/%s`
private static readonly DOWNLOAD_CDN_URL: string = "https://edge.forgecdn.net/files/%s/%s/%s";
private static readonly MINECRAFT_ID: number = 432; private static readonly MINECRAFT_ID: number = 432;
private static readonly FABRIC_TYPE: number = 4; private static readonly FABRIC_TYPE: number = 4;
getLatestVersion(id: string, mcVersion: string): Promise<Version> { async getLatestVersion(id: string, mcVersion: string): Promise<Version> {
const response = await const modResponse = await this.makeRequest(format(CurseforgeSource.GET_MOD_URL, id));
const latestFiles: Array<any> = modResponse.data.latestFilesIndexes;
return Promise.resolve(undefined); const latestFilesArr = latestFiles.filter(file => file.gameVersion === mcVersion);
if (Util.isArrayEmpty(latestFilesArr)) {
throw new ModNotFoundError(`Mod with id ${id} has no available versions on ${this.getSourceName()} for Minecraft version ${mcVersion}`)
}
const fileId = latestFilesArr[0].fileId;
const fileResponse = await this.makeRequest(format(CurseforgeSource.GET_FILE_URL, id, fileId))
const fileObj = fileResponse.data;
const dependencies = [];
if (!Util.isArrayEmpty(fileObj.dependencies)) {
for (let dependency of fileObj.dependencies) {
// If dependency is required
if (dependency.relationType == 3) {
dependencies.push(await this.getLatestVersion(dependency.modId, mcVersion));
}
}
}
const downloadUrl = fileObj.downloadUrl != null ? fileObj.downloadUrl : this.constructDownloadUrl(id, fileObj.fileName);
return {
modId: id.toString(),
fileName: fileObj.fileName,
url: downloadUrl,
versionNumber: fileObj.displayName,
dependencies: dependencies
}
} }
getProjectName(id: string): Promise<string> { async getProjectName(id: string): Promise<string> {
return Promise.resolve(""); const response = await this.makeRequest(format(CurseforgeSource.GET_MOD_URL, id))
return response.data.name;
} }
getSourceName(): string { getSourceName(): string {
return "Curseforge"; return "Curseforge";
} }
install(version: Version, essential: boolean): Promise<Mod> { async install(version: Version, essential: boolean): Promise<void> {
return Promise.resolve(undefined); try {
if (Mods.isModInstalled(version.modId)) {
return;
}
const dependencies = [];
if (!Util.isArrayEmpty(version.dependencies)) {
for (let dependency of version.dependencies) {
await this.install(dependency, essential);
dependencies.push(dependency.modId)
}
}
FileDownloader.downloadMod(version)
const mod = {
name: await this.getProjectName(version.modId),
id: version.modId,
fileName: version.fileName,
version: version.versionNumber,
source: this.getSourceName(),
essential: essential,
dependencies: dependencies
}
Mods.trackMod(mod);
} catch (e) {
throw new DownloadError(`An error occurred downloading mod with id ${version.modId} from ${this.getSourceName()}`)
}
} }
async search(query: string): Promise<string> { async search(query: string): Promise<string> {
@ -40,21 +104,24 @@ export class CurseforgeSource implements ModSource {
} }
const response = await this.makeRequest(CurseforgeSource.SEARCH_URL, params); const response = await this.makeRequest(CurseforgeSource.SEARCH_URL, params);
const results = response.data;
const id = response.data[0].id; if (Util.isArrayEmpty(results)) {
if (id == undefined) {
throw new ModNotFoundError(`Mod ${query} could not be found on ${this.getSourceName()}`); throw new ModNotFoundError(`Mod ${query} could not be found on ${this.getSourceName()}`);
} }
return id; return results[0].id;
} }
private async makeRequest(url: string, params: object) { private async makeRequest(url: string, params?: object) {
if (process.env.CURSEFORGE_API_KEY == undefined) { if (process.env.CURSEFORGE_API_KEY == undefined) {
throw new Error("Attempted Curseforge api calls with undefined api key environment variable (CURSEFORGE_API_KEY)") throw new Error("Attempted Curseforge api calls with undefined api key environment variable (CURSEFORGE_API_KEY)")
} }
if (params == undefined) {
params = {}
}
const response = await axios.get(url, { const response = await axios.get(url, {
headers: { headers: {
"x-api-key": process.env.CURSEFORGE_API_KEY "x-api-key": process.env.CURSEFORGE_API_KEY
@ -64,5 +131,18 @@ export class CurseforgeSource implements ModSource {
return await response.data; return await response.data;
} }
private constructDownloadUrl(id: string, fileName: string) {
// Some mods have a null download link. Download links follow a pattern such that we can
// create the URL ourselves in those rare cases. If download link is invalid, download
// will gracefully fail
const first = id.toString().substring(0, 4);
let last = id.toString().substring(4);
if (last.charAt(0) == '0') {
last = last.replace("0", "");
}
return format(CurseforgeSource.DOWNLOAD_CDN_URL, first, last, fileName);
}
} }
*/