From c7c2ca786bd050043dc36f5a35fbe7699a29adbc Mon Sep 17 00:00:00 2001 From: Tricked <72335827+Tricked-dev@users.noreply.github.com> Date: Sat, 27 Aug 2022 01:08:25 +0200 Subject: [PATCH] feat: add command for arbitrary URLs (#137) * feat: install command for direct downloads * use sha1 instead of sha256 * apply suggestions * feat: parse urls instead of using hasprefix * stop by default and add force flag * Implement various fixes and improvements Co-authored-by: Tricked <72335827+SkyBlockDev@users.noreply.github.com> Co-authored-by: comp500 --- core/mod.go | 20 +++++- main.go | 1 + modrinth/install.go | 2 +- url/install.go | 152 ++++++++++++++++++++++++++++++++++++++++++++ url/url.go | 15 +++++ 5 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 url/install.go create mode 100644 url/url.go diff --git a/core/mod.go b/core/mod.go index 69a86a9..338042e 100644 --- a/core/mod.go +++ b/core/mod.go @@ -6,6 +6,8 @@ import ( "io" "os" "path/filepath" + "regexp" + "strings" ) // Mod stores metadata about a mod. This is written to a TOML file for each mod. @@ -44,7 +46,7 @@ type ModOption struct { } // The three possible values of Side (the side that the mod is on) are "server", "client", and "both". -//noinspection GoUnusedConst +// noinspection GoUnusedConst const ( ServerSide = "server" ClientSide = "client" @@ -129,3 +131,19 @@ func (m Mod) GetFilePath() string { func (m Mod) GetDestFilePath() string { return filepath.Join(filepath.Dir(m.metaFile), filepath.FromSlash(m.FileName)) } + +var slugifyRegex1 = regexp.MustCompile("\\(.*\\)") +var slugifyRegex2 = regexp.MustCompile(" - .+") +var slugifyRegex3 = regexp.MustCompile("[^a-z\\d]") +var slugifyRegex4 = regexp.MustCompile("-+") +var slugifyRegex5 = regexp.MustCompile("^-|-$") + +func SlugifyName(name string) string { + lower := strings.ToLower(name) + noBrackets := slugifyRegex1.ReplaceAllString(lower, "") + noSuffix := slugifyRegex2.ReplaceAllString(noBrackets, "") + limitedChars := slugifyRegex3.ReplaceAllString(noSuffix, "-") + noDuplicateDashes := slugifyRegex4.ReplaceAllString(limitedChars, "-") + noLeadingTrailingDashes := slugifyRegex5.ReplaceAllString(noDuplicateDashes, "") + return noLeadingTrailingDashes +} diff --git a/main.go b/main.go index 4a5d740..0a94e1d 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "github.com/packwiz/packwiz/cmd" _ "github.com/packwiz/packwiz/curseforge" _ "github.com/packwiz/packwiz/modrinth" + _ "github.com/packwiz/packwiz/url" _ "github.com/packwiz/packwiz/utils" ) diff --git a/modrinth/install.go b/modrinth/install.go index 06a386a..0d75e21 100644 --- a/modrinth/install.go +++ b/modrinth/install.go @@ -391,7 +391,7 @@ func createFileMeta(mod *modrinthApi.Project, version *modrinthApi.Version, file if mod.Slug != nil { path = modMeta.SetMetaPath(filepath.Join(viper.GetString("meta-folder-base"), folder, *mod.Slug+core.MetaExtension)) } else { - path = modMeta.SetMetaPath(filepath.Join(viper.GetString("meta-folder-base"), folder, *mod.Title+core.MetaExtension)) + path = modMeta.SetMetaPath(filepath.Join(viper.GetString("meta-folder-base"), folder, core.SlugifyName(*mod.Title)+core.MetaExtension)) } // If the file already exists, this will overwrite it!!! diff --git a/url/install.go b/url/install.go new file mode 100644 index 0000000..9213402 --- /dev/null +++ b/url/install.go @@ -0,0 +1,152 @@ +package url + +import ( + "fmt" + "github.com/packwiz/packwiz/core" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "io" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "strings" +) + +var installCmd = &cobra.Command{ + Use: "add [name] [url]", + Short: "Add an external file from a direct download link, for sites that are not directly supported by packwiz", + Aliases: []string{"install", "get"}, + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + pack, err := core.LoadPack() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + dl, err := url.Parse(args[1]) + if err != nil { + fmt.Println("Failed to parse URL:", err) + os.Exit(1) + } + if dl.Scheme != "https" && dl.Scheme != "http" { + fmt.Println("Unsupported URL scheme:", dl.Scheme) + os.Exit(1) + } + + // TODO: consider using colors for these warnings but those can have issues on windows + force, err := cmd.Flags().GetBool("force") + if !force && err == nil { + var msg string + // TODO: update when github command is added + // TODO: make this generic? + //if dl.Host == "www.github.com" || dl.Host == "github.com" { + // msg = "github add " + args[1] + //} + if strings.HasSuffix(dl.Host, "modrinth.com") { + msg = "modrinth add " + args[1] + } + if strings.HasSuffix(dl.Host, "curseforge.com") || strings.HasSuffix(dl.Host, "forgecdn.net") { + msg = "curseforge add " + args[1] + } + if msg != "" { + fmt.Println("Consider using packwiz", msg, "instead; if you know what you are doing use --force to add this file without update metadata.") + os.Exit(1) + } + } + + hash, err := getSha1(args[1]) + if err != nil { + fmt.Println("Failed to retrieve SHA1 hash for file", err) + os.Exit(1) + } + + index, err := pack.LoadIndex() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + filename := path.Base(dl.Path) + modMeta := core.Mod{ + Name: args[0], + FileName: filename, + Download: core.ModDownload{ + URL: args[1], + HashFormat: "sha1", + Hash: hash, + }, + } + + folder := viper.GetString("meta-folder") + if folder == "" { + folder = "mods" + } + destPathName, err := cmd.Flags().GetString("meta-name") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + if destPathName == "" { + destPathName = core.SlugifyName(args[0]) + } + destPath := modMeta.SetMetaPath(filepath.Join(viper.GetString("meta-folder-base"), folder, + destPathName+core.MetaExtension)) + + format, hash, err := modMeta.Write() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = index.RefreshFileWithHash(destPath, format, hash, true) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = index.Write() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = pack.UpdateIndexHash() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = pack.Write() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Printf("Successfully added %s (%s) from: %s\n", args[0], destPath, args[1]) + }} + +func getSha1(url string) (string, error) { + // TODO: hook up to existing cache system? might not be that useful + mainHasher, err := core.GetHashImpl("sha1") + resp, err := http.Get(url) + if err != nil { + return "", err + } + + defer resp.Body.Close() + if resp.StatusCode != 200 { + return "", fmt.Errorf("failed to download: unexpected response status: %v", resp.Status) + } + + _, err = io.Copy(mainHasher, resp.Body) + if err != nil { + return "", err + } + + return mainHasher.HashToString(mainHasher.Sum(nil)), nil +} + +func init() { + urlCmd.AddCommand(installCmd) + + installCmd.Flags().Bool("force", false, "Add a file even if the download URL is supported by packwiz in an alternative command (which may support dependencies and updates)") + installCmd.Flags().String("meta-name", "", "Filename to use for the created metadata file (defaults to a name generated from the name you supply)") +} diff --git a/url/url.go b/url/url.go new file mode 100644 index 0000000..10cc31e --- /dev/null +++ b/url/url.go @@ -0,0 +1,15 @@ +package url + +import ( + "github.com/packwiz/packwiz/cmd" + "github.com/spf13/cobra" +) + +var urlCmd = &cobra.Command{ + Use: "url", + Short: "Add external files from a direct download link, for sites that are not directly supported by packwiz", +} + +func init() { + cmd.Add(urlCmd) +}