mirror of
https://github.com/packwiz/packwiz.git
synced 2025-04-19 21:16:30 +02:00
261 lines
6.0 KiB
Go
261 lines
6.0 KiB
Go
package modrinth
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"github.com/spf13/viper"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/comp500/packwiz/core"
|
|
"github.com/spf13/cobra"
|
|
"gopkg.in/dixonwille/wmenu.v4"
|
|
)
|
|
|
|
var modSiteRegex = regexp.MustCompile("modrinth\\.com/mod/([^/]+)/?$")
|
|
var versionSiteRegex = regexp.MustCompile("modrinth\\.com/mod/([^/]+)/version/([^/]+)/?$")
|
|
|
|
// installCmd represents the install command
|
|
var installCmd = &cobra.Command{
|
|
Use: "install [mod]",
|
|
Short: "Install a mod from a modrinth URL, slug, ID or search",
|
|
Aliases: []string{"add", "get"},
|
|
Args: cobra.ArbitraryArgs,
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
pack, err := core.LoadPack()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if len(args) == 0 || len(args[0]) == 0 {
|
|
fmt.Println("You must specify a mod.")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// If there are more than 1 argument, go straight to searching - URLs/Slugs should not have spaces!
|
|
if len(args) > 1 {
|
|
err = installViaSearch(strings.Join(args, " "), pack)
|
|
if err != nil {
|
|
fmt.Printf("Failed installing mod: %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
return
|
|
}
|
|
|
|
//Try interpreting the arg as a version url
|
|
matches := versionSiteRegex.FindStringSubmatch(args[0])
|
|
if matches != nil && len(matches) == 3 {
|
|
err = installVersionById(matches[2], pack)
|
|
if err != nil {
|
|
fmt.Printf("Failed installing mod: %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
return
|
|
}
|
|
|
|
//Try interpreting the arg as a modId or slug.
|
|
//Modrinth transparently handles slugs/mod ids in their api; we don't have to detect which one it is.
|
|
var modStr string
|
|
|
|
//Try to see if it's a site, if extract the id/slug from the url.
|
|
//Otherwise, interpret the arg as a id/slug straight up
|
|
matches = modSiteRegex.FindStringSubmatch(args[0])
|
|
if matches != nil && len(matches) == 2 {
|
|
modStr = matches[1]
|
|
} else {
|
|
modStr = args[0]
|
|
}
|
|
|
|
mod, err := fetchMod(modStr)
|
|
|
|
if err == nil {
|
|
//We found a mod with that id/slug
|
|
err = installMod(mod, pack)
|
|
if err != nil {
|
|
fmt.Printf("Failed installing mod: %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
return
|
|
} else {
|
|
//This wasn't a valid modid/slug, try to search for it instead:
|
|
//Don't bother to search if it looks like a url though
|
|
if matches == nil {
|
|
err = installViaSearch(args[0], pack)
|
|
if err != nil {
|
|
fmt.Printf("Failed installing mod: %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
} else {
|
|
fmt.Printf("Failed installing mod: %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
},
|
|
}
|
|
|
|
func installViaSearch(query string, pack core.Pack) error {
|
|
mcVersion, err := pack.GetMCVersion()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
results, err := getModIdsViaSearch(query, append([]string{mcVersion}, viper.GetStringSlice("acceptable-game-versions")...))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
//Create menu for the user to choose the correct mod
|
|
menu := wmenu.NewMenu("Choose a number:")
|
|
menu.Option("Cancel", nil, false, nil)
|
|
for i, v := range results {
|
|
menu.Option(v.Title, v, i == 0, nil)
|
|
}
|
|
|
|
menu.Action(func(menuRes []wmenu.Opt) error {
|
|
if len(menuRes) != 1 || menuRes[0].Value == nil {
|
|
return errors.New("Cancelled!")
|
|
}
|
|
|
|
//Get the selected mod
|
|
selectedMod, ok := menuRes[0].Value.(ModResult)
|
|
if !ok {
|
|
return errors.New("error converting interface from wmenu")
|
|
}
|
|
|
|
//Install the selected mod
|
|
modId := strings.TrimPrefix(selectedMod.ModID, "local-")
|
|
|
|
mod, err := fetchMod(modId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return installMod(mod, pack)
|
|
})
|
|
|
|
return menu.Run()
|
|
}
|
|
|
|
func installMod(mod Mod, pack core.Pack) error {
|
|
fmt.Printf("Found mod %s: '%s'.\n", mod.Title, mod.Description)
|
|
|
|
latestVersion, err := getLatestVersion(mod.ID, pack)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if latestVersion.ID == "" {
|
|
return errors.New("mod is not available for this Minecraft version (use the acceptable-game-versions option to accept more) or mod loader")
|
|
}
|
|
|
|
return installVersion(mod, latestVersion, pack)
|
|
}
|
|
|
|
func installVersion(mod Mod, version Version, pack core.Pack) error {
|
|
var files = version.Files
|
|
|
|
if len(files) == 0 {
|
|
return errors.New("version doesn't have any files attached")
|
|
}
|
|
|
|
// TODO: add some way to allow users to pick which file to install?
|
|
var file = files[0]
|
|
// Prefer the primary file
|
|
for _, v := range files {
|
|
if v.Primary {
|
|
file = v
|
|
}
|
|
}
|
|
|
|
//Install the file
|
|
fmt.Printf("Installing %s from version %s\n", file.Filename, version.VersionNumber)
|
|
index, err := pack.LoadIndex()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
updateMap := make(map[string]map[string]interface{})
|
|
|
|
updateMap["modrinth"], err = mrUpdateData{
|
|
ModID: mod.ID,
|
|
InstalledVersion: version.ID,
|
|
}.ToMap()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
side := mod.getSide()
|
|
if side == "" {
|
|
return errors.New("version doesn't have a side that's supported. Server: " + mod.ServerSide + " Client: " + mod.ClientSide)
|
|
}
|
|
|
|
algorithm, hash := file.getBestHash()
|
|
if algorithm == "" {
|
|
return errors.New("file doesn't have a hash")
|
|
}
|
|
|
|
modMeta := core.Mod{
|
|
Name: mod.Title,
|
|
FileName: file.Filename,
|
|
Side: side,
|
|
Download: core.ModDownload{
|
|
URL: file.Url,
|
|
HashFormat: algorithm,
|
|
Hash: hash,
|
|
},
|
|
Update: updateMap,
|
|
}
|
|
var path string
|
|
if mod.Slug != "" {
|
|
path = modMeta.SetMetaName(mod.Slug, index)
|
|
} else {
|
|
path = modMeta.SetMetaName(mod.Title, index)
|
|
}
|
|
|
|
// If the file already exists, this will overwrite it!!!
|
|
// TODO: Should this be improved?
|
|
// Current strategy is to go ahead and do stuff without asking, with the assumption that you are using
|
|
// VCS anyway.
|
|
|
|
format, hash, err := modMeta.Write()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = index.RefreshFileWithHash(path, format, hash, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = index.Write()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = pack.UpdateIndexHash()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = pack.Write()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func installVersionById(versionId string, pack core.Pack) error {
|
|
version, err := fetchVersion(versionId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mod, err := fetchMod(version.ModID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return installVersion(mod, version, pack)
|
|
}
|
|
|
|
func init() {
|
|
modrinthCmd.AddCommand(installCmd)
|
|
}
|