Added functionality to mark mods as essential

This commit is contained in:
Kallum Jones 2022-08-04 16:04:21 +01:00
parent ab83374735
commit e913bafb1e
No known key found for this signature in database
GPG Key ID: D7F4589C4D7F81A9
11 changed files with 106 additions and 29 deletions

13
package-lock.json generated
View File

@ -15,7 +15,8 @@
"commander": "^9.4.0", "commander": "^9.4.0",
"ora": "^6.1.2", "ora": "^6.1.2",
"pino": "^8.3.1", "pino": "^8.3.1",
"string-format": "^2.0.0" "string-format": "^2.0.0",
"string-similarity-js": "^2.1.4"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.6.3", "@types/node": "^18.6.3",
@ -546,6 +547,11 @@
"resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz", "resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz",
"integrity": "sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==" "integrity": "sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA=="
}, },
"node_modules/string-similarity-js": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/string-similarity-js/-/string-similarity-js-2.1.4.tgz",
"integrity": "sha512-uApODZNjCHGYROzDSAdCmAHf60L/pMDHnP/yk6TAbvGg7JSPZlSto/ceCI7hZEqzc53/juU2aOJFkM2yUVTMTA=="
},
"node_modules/strip-ansi": { "node_modules/strip-ansi": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz",
@ -946,6 +952,11 @@
"resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz", "resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz",
"integrity": "sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==" "integrity": "sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA=="
}, },
"string-similarity-js": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/string-similarity-js/-/string-similarity-js-2.1.4.tgz",
"integrity": "sha512-uApODZNjCHGYROzDSAdCmAHf60L/pMDHnP/yk6TAbvGg7JSPZlSto/ceCI7hZEqzc53/juU2aOJFkM2yUVTMTA=="
},
"strip-ansi": { "strip-ansi": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz",

View File

@ -17,7 +17,8 @@
"commander": "^9.4.0", "commander": "^9.4.0",
"ora": "^6.1.2", "ora": "^6.1.2",
"pino": "^8.3.1", "pino": "^8.3.1",
"string-format": "^2.0.0" "string-format": "^2.0.0",
"string-similarity-js": "^2.1.4"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.6.3", "@types/node": "^18.6.3",

View File

@ -0,0 +1,20 @@
import Subcommand from "./subcommand.js";
import {Command} from "commander";
import ModManager from "../mod-manager.js";
import Mods from "../mods/mods.js";
export default class EssentialCommand implements Subcommand {
registerCommand(program: Command): void {
program.command("essential")
.description("Marks mods as essential")
.argument("<mods...>", "The mods to mark as essential (as names or ids)")
.action((mods) => {
ModManager.execute(() => {
for (let mod of mods) {
Mods.markEssential(mod)
}
})
})
}
}

View File

@ -8,10 +8,11 @@ export default class InstallCommand implements Subcommand {
program.command("install") program.command("install")
.description("Installs the provided mods") .description("Installs the provided mods")
.argument("<mods...>", "The mods to install") .argument("<mods...>", "The mods to install")
.action((mods) => { .option("-e, --essential", "Marks these mods as essential", false)
.action(function() {
ModManager.execute(async () => { ModManager.execute(async () => {
for (const mod of mods) { for (const mod of this.args) {
await Mods.install(mod); await Mods.install(mod, this.opts().essential);
} }
}) })
}); });

View File

@ -7,7 +7,7 @@ export default class UninstallCommand implements Subcommand {
registerCommand(program: Command): void { registerCommand(program: Command): void {
program.command("uninstall") program.command("uninstall")
.description("Uninstalls the provided mods") .description("Uninstalls the provided mods")
.argument("<mods...>") .argument("<mods...>", "The mods to uninstall (as names or ids)")
.action((mods) => { .action((mods) => {
ModManager.execute(() => { ModManager.execute(() => {
for (let mod of mods) { for (let mod of mods) {

View File

@ -9,6 +9,7 @@ import path from "path";
import {Logger, pino} from "pino" import {Logger, pino} from "pino"
import {ListCommand} from "./commands/list_command.js"; import {ListCommand} from "./commands/list_command.js";
import UninstallCommand from "./commands/uninstall_command.js"; import UninstallCommand from "./commands/uninstall_command.js";
import EssentialCommand from "./commands/essential_command.js";
export default class ModManager { export default class ModManager {
@ -21,7 +22,8 @@ export default class ModManager {
new InitCommand(), new InitCommand(),
new InstallCommand(), new InstallCommand(),
new ListCommand(), new ListCommand(),
new UninstallCommand() new UninstallCommand(),
new EssentialCommand()
]; ];
static init() { static init() {

2
src/mods/mod.d.ts vendored
View File

@ -1,10 +1,12 @@
declare global { declare global {
// DONT FORGET TO UPDATE CONSTRUCTORS WHEN MOD SIGNATURE CHANGES
type Mod = { type Mod = {
id: string id: string
name: string name: string
fileName: string, fileName: string,
version: string version: string
source: string, source: string,
essential: boolean
} }
} }

View File

@ -15,7 +15,7 @@ export default class Mods {
new ModrinthSource() new ModrinthSource()
]; ];
public static async install(mod: string): Promise<void> { public static async install(mod: string, essential: boolean): Promise<void> {
let success: boolean = false; let success: boolean = false;
// Go through each mod source // Go through each mod source
@ -47,7 +47,7 @@ export default class Mods {
if (!this.isModInstalled(id)) { if (!this.isModInstalled(id)) {
spinner.updateText(`Installing ${projectName}...`) spinner.updateText(`Installing ${projectName}...`)
try { try {
const modObj: Mod = await source.install(id); const modObj: Mod = await source.install(id, essential);
this.trackMod(modObj); this.trackMod(modObj);
spinner.succeed(`Successfully installed ${projectName}`); spinner.succeed(`Successfully installed ${projectName}`);
@ -94,30 +94,17 @@ export default class Mods {
} }
static uninstall(mod: string) { static uninstall(mod: string) {
let mods: Array<Mod> = this.getTrackedMods();
// Replace underscores with spaces
mod = mod.replaceAll("_", " ");
// Find mod to uninstall // Find mod to uninstall
const spinner = new PrintUtils.Spinner(`Uninstalling ${mod}...`) const spinner = new PrintUtils.Spinner(`Uninstalling ${mod}...`)
let modToUninstall: Mod | undefined = undefined;
for (let modEle of mods) {
const id = modEle.id.toLowerCase();
const name = modEle.name.toLowerCase();
const query = mod.toLowerCase();
if (id == query || name == query) {
modToUninstall = modEle;
break;
}
}
const modToUninstall = this.findMod(mod);
// IF a matching mod is found, remove it // IF a matching mod is found, remove it
if (modToUninstall != undefined) { if (modToUninstall != undefined) {
let mods: Array<Mod> = this.getTrackedMods();
// Remove mod from list and uninstall it // Remove mod from list and uninstall it
unlinkSync(path.join(this.MODS_FOLDER_PATH, modToUninstall.fileName)); unlinkSync(path.join(this.MODS_FOLDER_PATH, modToUninstall.fileName));
mods = mods.filter(item => item !== modToUninstall); mods = mods.filter(item => !Mods.areModsEqual(item, modToUninstall));
this.writeFile(mods); this.writeFile(mods);
spinner.succeed(`${modToUninstall.name} successfully uninstalled!`) spinner.succeed(`${modToUninstall.name} successfully uninstalled!`)
} else { } else {
@ -127,5 +114,46 @@ export default class Mods {
}
static areModsEqual(mod1: Mod, mod2: Mod): boolean {
return mod1.id === mod2.id;
}
static markEssential(mod: string) {
const modToMark = this.findMod(mod);
if (modToMark != undefined) {
let mods = this.getTrackedMods();
// Remove mod from list
mods = mods.filter(item => !Mods.areModsEqual(item, modToMark));
// Mark is as essential, and read it
modToMark.essential = true;
mods.push(modToMark)
this.writeFile(mods);
PrintUtils.success(`Marked ${modToMark.name} as essential`)
} else {
PrintUtils.error(`${mod} not found.`)
}
}
private static findMod(mod: string): Mod | undefined {
// Replace underscores with spaces
mod = mod.replaceAll("_", " ");
let mods: Array<Mod> = this.getTrackedMods();
for (let modEle of mods) {
const id = modEle.id.toLowerCase();
const name = modEle.name.toLowerCase();
const query = mod.toLowerCase();
if (id == query || Util.areStringsSimilar(mod, name)) {
return modEle;
}
}
return undefined;
} }
} }

View File

@ -1,7 +1,7 @@
export default interface ModSource { export default interface ModSource {
search(query: string): Promise<string>; search(query: string): Promise<string>;
install(id: string): Promise<Mod>; install(id: string, essential: boolean): Promise<Mod>;
getSourceName(): string; getSourceName(): string;

View File

@ -124,10 +124,11 @@ export default class ModrinthSource implements ModSource {
* } * }
* ] * ]
* @param id the id of the mod * @param id the id of the mod
* @param essential whether this mod is essential or not
* @throws DownloadError if an error occurs when downloading * @throws DownloadError if an error occurs when downloading
* @throws ModNotFoundError if there are no versions available for the current Minecraft Version * @throws ModNotFoundError if there are no versions available for the current Minecraft Version
*/ */
async install(id: string): Promise<Mod> { async install(id: string, essential: boolean): Promise<Mod> {
const mcVersion = await MinecraftUtils.getCurrentMinecraftVersion(); const mcVersion = await MinecraftUtils.getCurrentMinecraftVersion();
const params = { const params = {
@ -161,7 +162,8 @@ export default class ModrinthSource implements ModSource {
id: id, id: id,
fileName: fileName, fileName: fileName,
version: modVersion, version: modVersion,
source: this.getSourceName() source: this.getSourceName(),
essential: essential
}; };
} catch (e) { } catch (e) {

View File

@ -1,4 +1,8 @@
import { stringSimilarity } from "string-similarity-js";
export default class Util { export default class Util {
private static readonly SIMILARITY_THRESHOLD: number = 0.8;
static isArrayEmpty(array: Array<any> | undefined): boolean { static isArrayEmpty(array: Array<any> | undefined): boolean {
return array === undefined || array.length == 0; return array === undefined || array.length == 0;
} }
@ -9,4 +13,10 @@ export default class Util {
// uppercase the first character // uppercase the first character
.replace(/^./, function(str){ return str.toUpperCase(); }) .replace(/^./, function(str){ return str.toUpperCase(); })
} }
static areStringsSimilar(master: string, compare: string): boolean {
master = master.toLowerCase();
compare = compare.toLowerCase();
return stringSimilarity(master, compare) >= this.SIMILARITY_THRESHOLD;
}
} }