mirror of
https://github.com/packwiz/packwiz.git
synced 2025-05-03 19:16:30 +02:00
Command to open page in browser; refactoring
This commit is contained in:
parent
ebc2115b41
commit
91693cd3eb
@ -85,3 +85,9 @@ func (m Mod) Write() (string, string, error) {
|
|||||||
hashString := hex.EncodeToString(h.Sum(nil))
|
hashString := hex.EncodeToString(h.Sum(nil))
|
||||||
return "sha256", hashString, err
|
return "sha256", hashString, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetParsedUpdater can be used to retrieve updater-specific information after parsing a mod file
|
||||||
|
func (m Mod) GetParsedUpdater(updaterName string) (Updater, bool) {
|
||||||
|
upd, ok := m.updaters[updaterName]
|
||||||
|
return upd, ok
|
||||||
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -9,7 +10,7 @@ const ModExtension = ".toml"
|
|||||||
|
|
||||||
// ResolveMod returns the path to a mod file from it's name
|
// ResolveMod returns the path to a mod file from it's name
|
||||||
func ResolveMod(modName string, flags Flags) string {
|
func ResolveMod(modName string, flags Flags) string {
|
||||||
|
// TODO: should this work for any metadata file?
|
||||||
fileName := strings.ToLower(strings.TrimSuffix(modName, ModExtension)) + ModExtension
|
fileName := strings.ToLower(strings.TrimSuffix(modName, ModExtension)) + ModExtension
|
||||||
return filepath.Join(flags.ModsFolder, fileName)
|
return filepath.Join(flags.ModsFolder, fileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +1,14 @@
|
|||||||
package curseforge
|
package curseforge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/agnivade/levenshtein"
|
|
||||||
"github.com/comp500/packwiz/core"
|
"github.com/comp500/packwiz/core"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"github.com/skratchdot/open-golang/open"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
"gopkg.in/dixonwille/wmenu.v4"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -32,6 +28,13 @@ func init() {
|
|||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
return cmdImport(core.FlagsFromContext(c), c.Args().Get(0))
|
return cmdImport(core.FlagsFromContext(c), c.Args().Get(0))
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
Name: "open",
|
||||||
|
Usage: "Open the project page for a curseforge mod in your browser",
|
||||||
|
Aliases: []string{"doc"},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
return cmdDoc(core.FlagsFromContext(c), c.Args().Get(0))
|
||||||
|
},
|
||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
core.UpdateParsers["curseforge"] = cfUpdateParser{}
|
core.UpdateParsers["curseforge"] = cfUpdateParser{}
|
||||||
@ -135,167 +138,26 @@ func createModFile(flags core.Flags, modInfo modInfo, fileInfo modFileInfo, inde
|
|||||||
return index.RefreshFileWithHash(path, format, hash, true)
|
return index.RefreshFileWithHash(path, format, hash, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdInstall(flags core.Flags, mod string, modArgsTail []string) error {
|
func cmdDoc(flags core.Flags, mod string) error {
|
||||||
if len(mod) == 0 {
|
if len(mod) == 0 {
|
||||||
return cli.NewExitError("You must specify a mod.", 1)
|
return cli.NewExitError("You must specify a mod.", 1)
|
||||||
}
|
}
|
||||||
pack, err := core.LoadPack(flags)
|
|
||||||
|
modData, err := core.LoadMod(core.ResolveMod(mod, flags))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(err, 1)
|
return cli.NewExitError(err, 1)
|
||||||
}
|
}
|
||||||
index, err := pack.LoadIndex()
|
updateData, ok := modData.GetParsedUpdater("curseforge")
|
||||||
|
if !ok {
|
||||||
|
return cli.NewExitError("This mod doesn't seem to be a curseforge mod!", 1)
|
||||||
|
}
|
||||||
|
cfUpdateData := updateData.(cfUpdater)
|
||||||
|
url := "https://minecraft.curseforge.com/projects/" + strconv.Itoa(cfUpdateData.ProjectID)
|
||||||
|
err = open.Start(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(err, 1)
|
fmt.Println("Opening page failed, direct link:")
|
||||||
|
fmt.Println(url)
|
||||||
}
|
}
|
||||||
mcVersion, err := pack.GetMCVersion()
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var done bool
|
|
||||||
var modID, fileID int
|
|
||||||
// If modArgsTail has anything, go straight to searching - URLs/Slugs should not have spaces!
|
|
||||||
if len(modArgsTail) == 0 {
|
|
||||||
done, modID, fileID, err = getFileIDsFromString(mod)
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !done {
|
|
||||||
done, modID, err = getModIDFromString(mod)
|
|
||||||
// Ignore error, go to search instead (e.g. lowercase to search instead of as a slug)
|
|
||||||
if err != nil {
|
|
||||||
done = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
modInfoObtained := false
|
|
||||||
var modInfoData modInfo
|
|
||||||
|
|
||||||
if !done {
|
|
||||||
fmt.Println("Searching CurseForge...")
|
|
||||||
modArgs := append([]string{mod}, modArgsTail...)
|
|
||||||
searchTerm := strings.Join(modArgs, " ")
|
|
||||||
// TODO: Curse search
|
|
||||||
// TODO: how to do interactive choices? automatically assume version? ask mod from list? choose first?
|
|
||||||
results, err := getSearch(searchTerm, mcVersion)
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(results) == 0 {
|
|
||||||
return cli.NewExitError(errors.New("no mods found"), 1)
|
|
||||||
} else if len(results) == 1 {
|
|
||||||
modInfoData = results[0]
|
|
||||||
modID = modInfoData.ID
|
|
||||||
modInfoObtained = true
|
|
||||||
done = true
|
|
||||||
} else {
|
|
||||||
// Find the closest value to the search term
|
|
||||||
sort.Slice(results, func(i, j int) bool {
|
|
||||||
return levenshtein.ComputeDistance(searchTerm, results[i].Name) < levenshtein.ComputeDistance(searchTerm, results[j].Name)
|
|
||||||
})
|
|
||||||
|
|
||||||
menu := wmenu.NewMenu("Choose a number:")
|
|
||||||
|
|
||||||
for i, v := range results {
|
|
||||||
menu.Option(v.Name, v, i == 0, nil)
|
|
||||||
}
|
|
||||||
menu.Option("Cancel", nil, false, nil)
|
|
||||||
|
|
||||||
menu.Action(func(menuRes []wmenu.Opt) error {
|
|
||||||
if len(menuRes) != 1 || menuRes[0].Value == nil {
|
|
||||||
fmt.Println("Cancelled!")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Why is variable shadowing a thing!!!!
|
|
||||||
var ok bool
|
|
||||||
modInfoData, ok = menuRes[0].Value.(modInfo)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("Error converting interface from wmenu")
|
|
||||||
}
|
|
||||||
modID = modInfoData.ID
|
|
||||||
modInfoObtained = true
|
|
||||||
done = true
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
err = menu.Run()
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !done {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !done {
|
|
||||||
if err == nil {
|
|
||||||
err = errors.New("Mod not found")
|
|
||||||
}
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !modInfoObtained {
|
|
||||||
modInfoData, err = getModInfo(modID)
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileInfoObtained := false
|
|
||||||
var fileInfoData modFileInfo
|
|
||||||
if fileID == 0 {
|
|
||||||
// TODO: how do we decide which version to use?
|
|
||||||
for _, v := range modInfoData.GameVersionLatestFiles {
|
|
||||||
// Choose "newest" version by largest ID
|
|
||||||
if v.GameVersion == mcVersion && v.ID > fileID {
|
|
||||||
fileID = v.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if fileID == 0 {
|
|
||||||
return cli.NewExitError(errors.New("no files available for current Minecraft version"), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The API also provides some files inline, because that's efficient!
|
|
||||||
for _, v := range modInfoData.LatestFiles {
|
|
||||||
if v.ID == fileID {
|
|
||||||
fileInfoObtained = true
|
|
||||||
fileInfoData = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !fileInfoObtained {
|
|
||||||
fileInfoData, err = getFileInfo(modID, fileID)
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = createModFile(flags, modInfoData, fileInfoData, &index)
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = index.Write()
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
err = pack.UpdateIndexHash()
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
err = pack.Write()
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Mod \"%s\" successfully installed! (%s)\n", modInfoData.Name, fileInfoData.FileName)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
177
curseforge/install.go
Normal file
177
curseforge/install.go
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
package curseforge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/agnivade/levenshtein"
|
||||||
|
"github.com/comp500/packwiz/core"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
"gopkg.in/dixonwille/wmenu.v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cmdInstall(flags core.Flags, mod string, modArgsTail []string) error {
|
||||||
|
if len(mod) == 0 {
|
||||||
|
return cli.NewExitError("You must specify a mod.", 1)
|
||||||
|
}
|
||||||
|
pack, err := core.LoadPack(flags)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
index, err := pack.LoadIndex()
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
mcVersion, err := pack.GetMCVersion()
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var done bool
|
||||||
|
var modID, fileID int
|
||||||
|
// If modArgsTail has anything, go straight to searching - URLs/Slugs should not have spaces!
|
||||||
|
if len(modArgsTail) == 0 {
|
||||||
|
done, modID, fileID, err = getFileIDsFromString(mod)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !done {
|
||||||
|
done, modID, err = getModIDFromString(mod)
|
||||||
|
// Ignore error, go to search instead (e.g. lowercase to search instead of as a slug)
|
||||||
|
if err != nil {
|
||||||
|
done = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modInfoObtained := false
|
||||||
|
var modInfoData modInfo
|
||||||
|
|
||||||
|
if !done {
|
||||||
|
fmt.Println("Searching CurseForge...")
|
||||||
|
modArgs := append([]string{mod}, modArgsTail...)
|
||||||
|
searchTerm := strings.Join(modArgs, " ")
|
||||||
|
// TODO: Curse search
|
||||||
|
// TODO: how to do interactive choices? automatically assume version? ask mod from list? choose first?
|
||||||
|
results, err := getSearch(searchTerm, mcVersion)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(results) == 0 {
|
||||||
|
return cli.NewExitError("No mods found!", 1)
|
||||||
|
} else if len(results) == 1 {
|
||||||
|
modInfoData = results[0]
|
||||||
|
modID = modInfoData.ID
|
||||||
|
modInfoObtained = true
|
||||||
|
done = true
|
||||||
|
} else {
|
||||||
|
// Find the closest value to the search term
|
||||||
|
sort.Slice(results, func(i, j int) bool {
|
||||||
|
return levenshtein.ComputeDistance(searchTerm, results[i].Name) < levenshtein.ComputeDistance(searchTerm, results[j].Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
menu := wmenu.NewMenu("Choose a number:")
|
||||||
|
|
||||||
|
for i, v := range results {
|
||||||
|
menu.Option(v.Name, v, i == 0, nil)
|
||||||
|
}
|
||||||
|
menu.Option("Cancel", nil, false, nil)
|
||||||
|
|
||||||
|
menu.Action(func(menuRes []wmenu.Opt) error {
|
||||||
|
if len(menuRes) != 1 || menuRes[0].Value == nil {
|
||||||
|
fmt.Println("Cancelled!")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Why is variable shadowing a thing!!!!
|
||||||
|
var ok bool
|
||||||
|
modInfoData, ok = menuRes[0].Value.(modInfo)
|
||||||
|
if !ok {
|
||||||
|
return cli.NewExitError("Error converting interface from wmenu", 1)
|
||||||
|
}
|
||||||
|
modID = modInfoData.ID
|
||||||
|
modInfoObtained = true
|
||||||
|
done = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
err = menu.Run()
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !done {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !done {
|
||||||
|
if err == nil {
|
||||||
|
return cli.NewExitError("No mods found!", 1)
|
||||||
|
}
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !modInfoObtained {
|
||||||
|
modInfoData, err = getModInfo(modID)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInfoObtained := false
|
||||||
|
var fileInfoData modFileInfo
|
||||||
|
if fileID == 0 {
|
||||||
|
// TODO: how do we decide which version to use?
|
||||||
|
for _, v := range modInfoData.GameVersionLatestFiles {
|
||||||
|
// Choose "newest" version by largest ID
|
||||||
|
if v.GameVersion == mcVersion && v.ID > fileID {
|
||||||
|
fileID = v.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fileID == 0 {
|
||||||
|
return cli.NewExitError("No files available for current Minecraft version!", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The API also provides some files inline, because that's efficient!
|
||||||
|
for _, v := range modInfoData.LatestFiles {
|
||||||
|
if v.ID == fileID {
|
||||||
|
fileInfoObtained = true
|
||||||
|
fileInfoData = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fileInfoObtained {
|
||||||
|
fileInfoData, err = getFileInfo(modID, fileID)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = createModFile(flags, modInfoData, fileInfoData, &index)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = index.Write()
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
err = pack.UpdateIndexHash()
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
err = pack.Write()
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Mod \"%s\" successfully installed! (%s)\n", modInfoData.Name, fileInfoData.FileName)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
1
go.mod
1
go.mod
@ -11,6 +11,7 @@ require (
|
|||||||
github.com/mattn/go-isatty v0.0.4 // indirect
|
github.com/mattn/go-isatty v0.0.4 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.1.2
|
github.com/mitchellh/mapstructure v1.1.2
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e
|
||||||
github.com/stretchr/testify v1.2.2 // indirect
|
github.com/stretchr/testify v1.2.2 // indirect
|
||||||
github.com/urfave/cli v1.20.0
|
github.com/urfave/cli v1.20.0
|
||||||
github.com/vbauerster/mpb/v4 v4.7.0
|
github.com/vbauerster/mpb/v4 v4.7.0
|
||||||
|
2
go.sum
2
go.sum
@ -20,6 +20,8 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz
|
|||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e h1:VAzdS5Nw68fbf5RZ8RDVlUvPXNU6Z3jtPCK/qvm4FoQ=
|
||||||
|
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||||
|
Loading…
x
Reference in New Issue
Block a user