diff --git a/Makefile b/Makefile index 3913cc0..7520609 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,21 @@ build: wallhaven_dl wallhaven_dl: $(shell find . -name '*.go') - go build -o wallhaven_dl . + go build -o dist/ . -run: - go run main.go +run: build + ./dist/wallhaven_dl tidy: go mod tidy clean: - rm -f wallhaven_dl + rm -rf dist uninstall: rm -f /usr/bin/wallhaven_dl + rm -f /usr/share/zsh/site-functions/_wallhaven_dl install: - cp wallhaven_dl /usr/bin - ./wallhaven_dl completion zsh > /usr/share/zsh/site-functions/_wallhaven_dl + cp ./dist/wallhaven_dl /usr/bin + cp ./completions/_wallhaven_dl /usr/share/zsh/site-functions/_wallhaven_dl diff --git a/cmd/root.go b/cmd/root.go deleted file mode 100644 index 696b09d..0000000 --- a/cmd/root.go +++ /dev/null @@ -1,21 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - - "github.com/spf13/cobra" -) - -var rootCmd = &cobra.Command{ - Use: "wallhaven_dl", - Short: "A wallpaper downloader and setter", -} - -// Execute executes the root command. -func Execute() { - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } -} diff --git a/cmd/search.go b/cmd/search.go deleted file mode 100644 index 51912a4..0000000 --- a/cmd/search.go +++ /dev/null @@ -1,170 +0,0 @@ -package cmd - -import ( - "fmt" - "math/rand" - "os" - "os/exec" - "path" - "time" - - "github.com/spf13/cobra" - - "git.asdf.cafe/abs3nt/wallhaven_dl/src/wallhaven" -) - -func init() { - rootCmd.AddCommand(searchCmd) - searchCmd.PersistentFlags().StringVarP( - &searchRange, - "range", - "r", - "1y", - "range for search.", - ) - searchCmd.PersistentFlags().StringVarP( - &searchPurity, - "purity", - "p", - "110", - "purity for the search.", - ) - searchCmd.PersistentFlags().StringVarP( - &searchCategories, - "categories", - "c", - "010", - "categories for the search.", - ) - searchCmd.PersistentFlags().StringVarP( - &searchSorting, - "sort", - "s", - "toplist", - "sort by for results, valid sorts: date_added, relevance, random, views, favorites, searchlist.", - ) - searchCmd.PersistentFlags().StringVarP( - &searchOrder, - "order", - "o", - "desc", - "sort order for results, valid sorts: asc desc.", - ) - searchCmd.PersistentFlags().IntVarP( - &searchPage, - "maxPage", - "m", - 5, - "number of pages to randomly choose wallpaper from.", - ) - searchCmd.PersistentFlags().StringSliceVar( - &searchRatios, - "ratios", - []string{"16x9", "16x10"}, - "ratios to search for.", - ) - searchCmd.PersistentFlags().StringVar( - &searchAtLeast, - "at-least", - "2560x1440", - "minimum resolution for results.", - ) - searchCmd.PersistentFlags().StringVarP( - &searchScript, - "script", - "t", - "", - "script to run after downloading the wallpaper", - ) - searchCmd.PersistentFlags().StringVarP( - &downloadPath, - "download-path", - "d", - "", - "directory to download the image too", - ) -} - -var ( - searchRange string - searchPurity string - searchCategories string - searchSorting string - searchOrder string - searchAtLeast string - searchScript string - downloadPath string - searchRatios []string - searchPage int - searchCmd = &cobra.Command{ - Use: "search", - Aliases: []string{"s"}, - Args: cobra.RangeArgs(0, 1), - Short: "Wallhaven downloader with the option to run a script after the image has been downloaded", - RunE: func(cmd *cobra.Command, args []string) error { - return search(args) - }, - } -) - -func search(args []string) error { - seed := rand.NewSource(time.Now().UnixNano()) - r := rand.New(seed) - s := &wallhaven.Search{ - Categories: searchCategories, - Purities: searchPurity, - Sorting: searchSorting, - Order: searchOrder, - TopRange: searchRange, - AtLeast: searchAtLeast, - Ratios: searchRatios, - Page: r.Intn(searchPage) + 1, - } - if len(args) > 0 { - s.Query = wallhaven.Q{ - Tags: []string{args[0]}, - } - } - results, err := wallhaven.SearchWallpapers(s) - if err != nil { - return err - } - resultPath, err := getOrDownload(results, r) - if err != nil { - return err - } - if searchScript != "" { - err = runScript(resultPath, searchScript) - if err != nil { - return err - } - } - return nil -} - -func getOrDownload(results *wallhaven.SearchResults, r *rand.Rand) (string, error) { - if len(results.Data) == 0 { - return "", fmt.Errorf("no wallpapers found") - } - homedir, _ := os.UserHomeDir() - if downloadPath == "" { - downloadPath = path.Join(homedir, "Pictures/Wallpapers") - } - result := results.Data[r.Intn(len(results.Data))] - fullPath := path.Join(downloadPath, path.Base(result.Path)) - if _, err := os.Stat(fullPath); err != nil { - err = result.Download(path.Join(downloadPath)) - if err != nil { - return "", err - } - } - return fullPath, nil -} - -func runScript(imgPath, script string) error { - _, err := exec.Command(script, imgPath).Output() - if err != nil { - return err - } - return nil -} diff --git a/completions/_wallhaven_dl b/completions/_wallhaven_dl new file mode 100644 index 0000000..06fb393 --- /dev/null +++ b/completions/_wallhaven_dl @@ -0,0 +1,20 @@ +#compdef wallhaven_dl + +_cli_zsh_autocomplete() { + local -a opts + local cur + cur=${words[-1]} + if [[ "$cur" == "-"* ]]; then + opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-shell-completion)}") + else + opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-shell-completion)}") + fi + + if [[ "${opts[1]}" != "" ]]; then + _describe 'values' opts + else + _files + fi +} + +compdef _cli_zsh_autocomplete wallhaven_dl diff --git a/go.mod b/go.mod index 07fe96f..18a592a 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,14 @@ module git.asdf.cafe/abs3nt/wallhaven_dl -go 1.21.3 +go 1.22.0 -require github.com/spf13/cobra v1.7.0 +require ( + github.com/spf13/cobra v1.7.0 + github.com/urfave/cli/v3 v3.0.0-alpha9 +) require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect ) diff --git a/go.sum b/go.sum index f3366a9..4c8b2c4 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,21 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/urfave/cli/v3 v3.0.0-alpha9 h1:P0RMy5fQm1AslQS+XCmy9UknDXctOmG/q/FZkUFnJSo= +github.com/urfave/cli/v3 v3.0.0-alpha9/go.mod h1:0kK/RUFHyh+yIKSfWxwheGndfnrvYSmYFVeKCh03ZUc= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 83a6e23..610007a 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,207 @@ package main -import "git.asdf.cafe/abs3nt/wallhaven_dl/cmd" +import ( + "context" + "fmt" + "log" + "math/rand" + "os" + "os/exec" + "path" + "time" + + "github.com/urfave/cli/v3" + + "git.asdf.cafe/abs3nt/wallhaven_dl/src/wallhaven" +) func main() { - cmd.Execute() + app := cli.Command{ + EnableShellCompletion: true, + Name: "wallhaven_dl", + Usage: "Download wallpapers from wallhaven.cc", + Action: func(ctx context.Context, c *cli.Command) error { + fmt.Println("cli") + return nil + }, + Commands: []*cli.Command{ + { + Name: "search", + Usage: "Search for wallpapers", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "range", + Aliases: []string{"r"}, + Value: "1y", + Validator: func(s string) error { + if s != "1d" && s != "3d" && s != "1w" && s != "1M" && s != "3M" && s != "6M" && s != "1y" { + return fmt.Errorf("range must be '1d', '3d', '1w', '1M', '3M', '6M' or '1y'") + } + return nil + }, + Usage: "Time range for top sorting", + }, + &cli.StringFlag{ + Name: "purity", + Aliases: []string{"p"}, + Value: "110", + Validator: func(s string) error { + if len(s) != 3 { + return fmt.Errorf("purity must be 3 characters long") + } + for _, c := range s { + if c != '0' && c != '1' { + return fmt.Errorf("purity must be 0 or 1") + } + } + return nil + }, + Usage: "Purity of the wallpapers", + }, + &cli.StringFlag{ + Name: "categories", + Aliases: []string{"c"}, + Value: "010", + Validator: func(s string) error { + if len(s) != 3 { + return fmt.Errorf("categories must be 3 characters long") + } + for _, c := range s { + if c != '0' && c != '1' { + return fmt.Errorf("categories must be 0 or 1") + } + } + return nil + }, + Usage: "Categories of the wallpapers", + }, + &cli.StringFlag{ + Name: "sort", + Aliases: []string{"s"}, + Value: "toplist", + Validator: func(s string) error { + if s != "relevance" && s != "random" && s != "date_added" && s != "views" && s != "favorites" && + s != "toplist" { + return fmt.Errorf( + "sort must be 'relevance', 'random', 'date_added', 'views', 'favorites' or 'toplist'", + ) + } + return nil + }, + Usage: "Sorting of the wallpapers", + }, + &cli.StringFlag{ + Name: "order", + Aliases: []string{"o"}, + Value: "desc", + Validator: func(s string) error { + if s != "asc" && s != "desc" { + return fmt.Errorf("order must be 'asc' or 'desc'") + } + return nil + }, + Usage: "Order of the wallpapers", + }, + &cli.IntFlag{ + Name: "page", + Aliases: []string{"pg"}, + Value: 5, + Usage: "Pages to search", + }, + &cli.StringSliceFlag{ + Name: "ratios", + Aliases: []string{"rt"}, + Value: []string{"16x9", "16x10"}, + Usage: "Ratios of the wallpapers", + }, + &cli.StringFlag{ + Name: "atLeast", + Aliases: []string{"al"}, + Value: "2560x1440", + Usage: "Minimum resolution", + }, + &cli.StringFlag{ + Name: "scriptPath", + Aliases: []string{"sp"}, + Value: "", + TakesFile: true, + Usage: "Path to the script to run after downloading", + }, + &cli.StringFlag{ + Name: "downloadPath", + Aliases: []string{"dp"}, + Value: os.Getenv("HOME") + "/Pictures/Wallpapers", + TakesFile: true, + Usage: "Path to download the wallpapers", + }, + }, + Action: func(ctx context.Context, c *cli.Command) error { + return search(c) + }, + }, + }, + } + if err := app.Run(context.Background(), os.Args); err != nil { + log.Fatal(err) + } +} + +func search(c *cli.Command) error { + seed := rand.NewSource(time.Now().UnixNano()) + r := rand.New(seed) + s := &wallhaven.Search{ + Categories: c.String("categories"), + Purities: c.String("purity"), + Sorting: c.String("sort"), + Order: c.String("order"), + TopRange: c.String("range"), + AtLeast: c.String("atLeast"), + Ratios: c.StringSlice("ratios"), + Page: c.Int("page"), + } + query := c.Args().First() + if query != "" { + s.Query = wallhaven.Q{ + Tags: []string{query}, + } + } + results, err := wallhaven.SearchWallpapers(s) + if err != nil { + return err + } + resultPath, err := getOrDownload(results, r, c.String("downloadPath")) + if err != nil { + return err + } + searchScript := c.String("scriptPath") + if searchScript != "" { + err = runScript(resultPath, searchScript) + if err != nil { + return err + } + } + return nil +} + +func getOrDownload(results *wallhaven.SearchResults, r *rand.Rand, downloadPath string) (string, error) { + if len(results.Data) == 0 { + return "", fmt.Errorf("no wallpapers found") + } + result := results.Data[r.Intn(len(results.Data))] + fullPath := path.Join(downloadPath, path.Base(result.Path)) + if _, err := os.Stat(fullPath); err != nil { + err = result.Download(path.Join(downloadPath)) + if err != nil { + return "", err + } + } + return fullPath, nil +} + +func runScript(imgPath, script string) error { + _, err := exec.Command(script, imgPath).Output() + if err != nil { + return err + } + return nil } diff --git a/src/wallhaven/search.go b/src/wallhaven/search.go index 08daf12..0cc9fbd 100644 --- a/src/wallhaven/search.go +++ b/src/wallhaven/search.go @@ -63,7 +63,7 @@ type Search struct { Resolutions []string Ratios []string Colors []string // Colors is an array of hex colors represented as strings in #RRGGBB format - Page int + Page int64 } func (s Search) toQuery() url.Values { @@ -96,7 +96,7 @@ func (s Search) toQuery() url.Values { v.Add("colors", strings.Join([]string(s.Colors), ",")) } if s.Page > 0 { - v.Add("page", strconv.Itoa(s.Page)) + v.Add("page", strconv.FormatInt(s.Page, 10)) } return v }