From ef98591d02152edc47f875660b1a2cbd00c65e8c Mon Sep 17 00:00:00 2001 From: comp500 Date: Sat, 11 May 2019 14:32:14 +0100 Subject: [PATCH] Actually write mod files but aaa it doesn't use the struct tags properly so things are bad --- core/mod.go | 46 +++++++++++++++------ core/pack.go | 2 +- curseforge/curseforge.go | 87 ++++++++++++++++++++++++++++++---------- curseforge/request.go | 1 + 4 files changed, 100 insertions(+), 36 deletions(-) diff --git a/core/mod.go b/core/mod.go index 9700bf3..aedb42d 100644 --- a/core/mod.go +++ b/core/mod.go @@ -1,23 +1,27 @@ package core import ( "errors" + "os" "github.com/BurntSushi/toml" ) // Mod stores metadata about a mod. This is written to a TOML file for each mod. type Mod struct { - metaFilename string // The filename for the metadata file, used as an ID - Name string `toml:"name"` - FileName string `toml:"filename"` - Side string `toml:"side,omitempty"` - Optional bool `toml:"optional,omitempty"` - Download struct { - URL string `toml:"url"` - HashFormat string `toml:"hash-format"` - Hash string `toml:"hash"` - } `toml:"download"` - Update map[string]interface{} `toml:"update"` + metaFile string // The file for the metadata file, used as an ID + Name string `toml:"name"` + FileName string `toml:"filename"` + Side string `toml:"side,omitempty"` + Optional bool `toml:"optional,omitempty"` + Download ModDownload `toml:"download"` + Update map[string]interface{} `toml:"update"` +} + +// ModDownload specifies how to download the mod file +type ModDownload struct { + URL string `toml:"url"` + HashFormat string `toml:"hash-format"` + Hash string `toml:"hash"` } // The three possible values of Side (the side that the mod is on) are "server", "client", and "both". @@ -46,10 +50,26 @@ func LoadMod(modFile string) (Mod, error) { return mod, errors.New("Update plugin " + k + " not found!") } } + mod.metaFile = modFile return mod, nil } -func (m Mod) Write() { - +// SetMetaName sets the mod metadata file from a given file name (to be put in the mods folder) +func (m *Mod) SetMetaName(metaName string, flags Flags) { + m.metaFile = ResolveMod(metaName, flags) +} + +// Write saves the mod file +func (m Mod) Write() error { + f, err := os.Create(m.metaFile) + if err != nil { + return err + } + defer f.Close() + + enc := toml.NewEncoder(f) + // Disable indentation + enc.Indent = "" + return enc.Encode(m) } diff --git a/core/pack.go b/core/pack.go index d2f347b..857e934 100644 --- a/core/pack.go +++ b/core/pack.go @@ -94,7 +94,7 @@ func (pack Pack) Write() error { func (pack Pack) GetMCVersion() (string, error) { mcVersion, ok := pack.Versions["minecraft"] if !ok { - return "", errors.New("No Minecraft version specified in modpack!") + return "", errors.New("no minecraft version specified in modpack") } return mcVersion, nil } diff --git a/curseforge/curseforge.go b/curseforge/curseforge.go index 9a26d0d..30b73a7 100644 --- a/curseforge/curseforge.go +++ b/curseforge/curseforge.go @@ -1,8 +1,10 @@ package curseforge import ( + "errors" "fmt" "regexp" "strconv" + "strings" "github.com/comp500/packwiz/core" "github.com/mitchellh/mapstructure" @@ -18,7 +20,7 @@ func init() { Usage: "Install a mod from a curseforge URL, slug or ID", Aliases: []string{"add", "get"}, Action: func(c *cli.Context) error { - return cmdInstall(core.FlagsFromContext(c), c.Args().Get(0)) + return cmdInstall(core.FlagsFromContext(c), c.Args().Get(0), c.Args().Tail()) }, }, { Name: "import", @@ -33,8 +35,8 @@ func init() { } var fileIDRegexes = [...]*regexp.Regexp{ - regexp.MustCompile("https?:\\/\\/minecraft\\.curseforge\\.com\\/projects\\/(.+)\\/files\\/(\\d+)"), - regexp.MustCompile("https?:\\/\\/(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/(.+)\\/download\\/(\\d+)"), + regexp.MustCompile("^https?:\\/\\/minecraft\\.curseforge\\.com\\/projects\\/(.+)\\/files\\/(\\d+)$"), + regexp.MustCompile("^https?:\\/\\/(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/(.+)\\/download\\/(\\d+)$"), } func getFileIDsFromString(mod string) (bool, int, int, error) { @@ -53,10 +55,10 @@ func getFileIDsFromString(mod string) (bool, int, int, error) { } var modSlugRegexes = [...]*regexp.Regexp{ - regexp.MustCompile("https?:\\/\\/minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)"), - regexp.MustCompile("https?:\\/\\/(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/([^\\/]+)"), + regexp.MustCompile("^https?:\\/\\/minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)$"), + regexp.MustCompile("^https?:\\/\\/(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/([^\\/]+)$"), // Exact slug matcher - regexp.MustCompile("[a-z][\\da-z\\-]{0,127}"), + regexp.MustCompile("^[a-z][\\da-z\\-]{0,127}$"), } func getModIDFromString(mod string) (bool, int, error) { @@ -87,7 +89,7 @@ func getModIDFromString(mod string) (bool, int, error) { return false, 0, nil } -func cmdInstall(flags core.Flags, mod string) error { +func cmdInstall(flags core.Flags, mod string, modArgsTail []string) error { if len(mod) == 0 { return cli.NewExitError("You must specify a mod.", 1) } @@ -111,24 +113,65 @@ func cmdInstall(flags core.Flags, mod string) error { if !done { done, modID, err = getModIDFromString(mod) - if err != nil { - return cli.NewExitError(err, 1) - } + // Handle error later (e.g. lowercase to search instead of as a slug) } - // TODO: fallback to CurseMeta search - // TODO: how to do interactive choices? automatically assume version? ask mod from list? choose first? + if !done { + modArgs := append([]string{mod}, modArgsTail...) + searchTerm := strings.Join(modArgs, " ") + // TODO: CurseMeta search + // TODO: how to do interactive choices? automatically assume version? ask mod from list? choose first? + fmt.Println(searchTerm) + } + + if !done { + if err == nil { + err = errors.New("Mod not found") + } + return cli.NewExitError(err, 1) + } fmt.Printf("ids: %d %d %v\n", modID, fileID, done) - if done { - fmt.Println(mcVersion) - info, err := getModInfo(modID) - fmt.Println(err) - fmt.Println(info) - _ = index + // TODO: get FileID if it isn't there + + fmt.Println(mcVersion) + modInfo, err := getModInfo(modID) + fmt.Println(err) + fmt.Println(modInfo) + _ = index + + if fileID == 0 { + return nil } - return nil + + fileInfo, err := getFileInfo(modID, fileID) + + updateMap := make(map[string]interface{}) + + updateMap["curseforge"] = cfUpdater{ + ProjectID: modID, + FileID: fileID, + // TODO: determine update channel + ReleaseChannel: "release", + } + + modMeta := core.Mod{ + Name: modInfo.Name, + FileName: fileInfo.FileName, + Side: core.UniversalSide, + Download: core.ModDownload{ + URL: fileInfo.DownloadURL, + HashFormat: "murmur2", + Hash: strconv.Itoa(fileInfo.Fingerprint), + }, + Update: updateMap, + } + modMeta.SetMetaName(modInfo.Slug, flags) + + fmt.Printf("%#v\n", modMeta) + + return modMeta.Write() } type cfUpdateParser struct{} @@ -140,9 +183,9 @@ func (u cfUpdateParser) ParseUpdate(updateUnparsed interface{}) (core.Updater, e } type cfUpdater struct { - ProjectID int `mapstructure:"project-id"` - FileID int `mapstructure:"file-id"` - ReleaseChannel string `mapstructure:"release-channel"` + ProjectID int `mapstructure:"project-id" toml:"project-id"` + FileID int `mapstructure:"file-id" toml:"file-id"` + ReleaseChannel string `mapstructure:"release-channel" toml:"release-channel"` } func (u cfUpdater) DoUpdate(mod core.Mod) (bool, error) { diff --git a/curseforge/request.go b/curseforge/request.go index 1e3d166..cb94a64 100644 --- a/curseforge/request.go +++ b/curseforge/request.go @@ -188,6 +188,7 @@ type modFileInfo struct { Length int `json:"fileLength"` FileType int `json:"releaseType"` // fileStatus? means latest/preferred? + DownloadURL string `json:"downloadUrl"` GameVersions []string `json:"gameVersion"` Fingerprint int `json:"packageFingerprint"` Dependencies []struct {