initial
This commit is contained in:
commit
22a02d75a5
19
Makefile
Normal file
19
Makefile
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
build: wallhaven_dl
|
||||||
|
|
||||||
|
wallhaven_dl: $(shell find . -name '*.go')
|
||||||
|
go build -o wallhaven_dl .
|
||||||
|
|
||||||
|
run:
|
||||||
|
go run main.go
|
||||||
|
|
||||||
|
tidy:
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f wallhaven_dl
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
rm -f /usr/bin/wallhaven_dl
|
||||||
|
|
||||||
|
install:
|
||||||
|
cp wallhaven_dl /usr/bin
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module main
|
||||||
|
|
||||||
|
go 1.21.3
|
||||||
|
|
||||||
|
require github.com/alecthomas/kong v0.8.1
|
8
go.sum
Normal file
8
go.sum
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0=
|
||||||
|
github.com/alecthomas/assert/v2 v2.1.0/go.mod h1:b/+1DI2Q6NckYi+3mXyH3wFb8qG37K/DuK80n7WefXA=
|
||||||
|
github.com/alecthomas/kong v0.8.1 h1:acZdn3m4lLRobeh3Zi2S2EpnXTd1mOL6U7xVml+vfkY=
|
||||||
|
github.com/alecthomas/kong v0.8.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U=
|
||||||
|
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
|
||||||
|
github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||||
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
161
main.go
Normal file
161
main.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alecthomas/kong"
|
||||||
|
|
||||||
|
"main/wallhaven"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cli struct {
|
||||||
|
Search struct {
|
||||||
|
Query string `arg:"" name:"query" help:"what to search for." type:"string"`
|
||||||
|
} `cmd:"" help:"search for wallpaper"`
|
||||||
|
Top struct {
|
||||||
|
Purity string `arg:"" name:"purity" optional:"" help:"purity of results"`
|
||||||
|
} `cmd:"" help:"random toplist wallpaper"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx := kong.Parse(&cli)
|
||||||
|
switch ctx.Command() {
|
||||||
|
case "search <query>":
|
||||||
|
err := searchAndSet(cli.Search.Query)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
case "top", "top <purity>":
|
||||||
|
err := setTop(cli.Top.Purity)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic(ctx.Command())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchAndSet(query string) error {
|
||||||
|
seed := rand.NewSource(time.Now().UnixNano())
|
||||||
|
r := rand.New(seed)
|
||||||
|
results, err := wallhaven.SearchWallpapers(&wallhaven.Search{
|
||||||
|
Query: wallhaven.Q{
|
||||||
|
Tags: []string{query},
|
||||||
|
},
|
||||||
|
Categories: "111",
|
||||||
|
Purities: "110",
|
||||||
|
Sorting: wallhaven.Relevance,
|
||||||
|
Order: wallhaven.Desc,
|
||||||
|
AtLeast: wallhaven.Resolution{Width: 2560, Height: 1400},
|
||||||
|
Ratios: []wallhaven.Ratio{
|
||||||
|
{Horizontal: 16, Vertical: 9},
|
||||||
|
{Horizontal: 16, Vertical: 10},
|
||||||
|
},
|
||||||
|
Page: 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
result, err := getOrDownload(results, r)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = setWallPaperAndRestartStuff(result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTop(purity string) error {
|
||||||
|
seed := rand.NewSource(time.Now().UnixNano())
|
||||||
|
r := rand.New(seed)
|
||||||
|
s := &wallhaven.Search{
|
||||||
|
Categories: "010",
|
||||||
|
Purities: "110",
|
||||||
|
Sorting: wallhaven.Toplist,
|
||||||
|
Order: wallhaven.Desc,
|
||||||
|
TopRange: "1y",
|
||||||
|
AtLeast: wallhaven.Resolution{Width: 2560, Height: 1400},
|
||||||
|
Ratios: []wallhaven.Ratio{
|
||||||
|
{Horizontal: 16, Vertical: 9},
|
||||||
|
{Horizontal: 16, Vertical: 10},
|
||||||
|
},
|
||||||
|
Page: r.Intn(5) + 1,
|
||||||
|
}
|
||||||
|
if purity != "" {
|
||||||
|
s.Purities = purity
|
||||||
|
}
|
||||||
|
results, err := wallhaven.SearchWallpapers(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
result, err := getOrDownload(results, r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = setWallPaperAndRestartStuff(result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setWallPaperAndRestartStuff(result wallhaven.Wallpaper) error {
|
||||||
|
homedir, _ := os.UserHomeDir()
|
||||||
|
_, err := exec.Command("wal", "--cols16", "-i", path.Join(homedir, "Pictures/Wallpapers", path.Base(result.Path)), "-n", "-a", "85").
|
||||||
|
Output()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = exec.Command("swww", "img", path.Join(homedir, "/Pictures/Wallpapers", path.Base(result.Path)), "--transition-step=20", "--transition-fps=60").
|
||||||
|
Output()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = exec.Command("restart_dunst").
|
||||||
|
Output()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = exec.Command("pywalfox", "update").
|
||||||
|
Output()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
source, err := os.Open(path.Join(homedir, ".cache/wal/discord-wal.theme.css"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer source.Close()
|
||||||
|
destination, err := os.Create(path.Join(homedir, ".config/Vencord/themes/discord-wal.theme.css"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(destination, source)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOrDownload(results *wallhaven.SearchResults, r *rand.Rand) (wallhaven.Wallpaper, error) {
|
||||||
|
if len(results.Data) == 0 {
|
||||||
|
return wallhaven.Wallpaper{}, fmt.Errorf("no wallpapers found")
|
||||||
|
}
|
||||||
|
homedir, _ := os.UserHomeDir()
|
||||||
|
result := results.Data[r.Intn(len(results.Data))]
|
||||||
|
if _, err := os.Stat(path.Join(homedir, "Pictures/Wallpapers", path.Base(result.Path))); err != nil {
|
||||||
|
err = result.Download(path.Join(homedir, "Pictures/Wallpapers"))
|
||||||
|
if err != nil {
|
||||||
|
return wallhaven.Wallpaper{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
318
wallhaven/search.go
Normal file
318
wallhaven/search.go
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
package wallhaven
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Search Types
|
||||||
|
|
||||||
|
// Category is an enum used to represent wallpaper categories
|
||||||
|
type Category string
|
||||||
|
|
||||||
|
// Purity is an enum used to represent
|
||||||
|
type Purity string
|
||||||
|
|
||||||
|
// Sort enum specifies the various sort types accepted by WH api
|
||||||
|
type Sort int
|
||||||
|
|
||||||
|
// Sort Enum Values
|
||||||
|
const (
|
||||||
|
DateAdded Sort = iota + 1
|
||||||
|
Relevance
|
||||||
|
Random
|
||||||
|
Views
|
||||||
|
Favorites
|
||||||
|
Toplist
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s Sort) String() string {
|
||||||
|
str := [...]string{"", "date_added", "relevance", "random", "views", "favorites", "toplist"}
|
||||||
|
return str[s]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order enum specifies the sort orders accepted by WH api
|
||||||
|
type Order int
|
||||||
|
|
||||||
|
// Sort Enum Values
|
||||||
|
const (
|
||||||
|
Desc Order = iota + 1
|
||||||
|
Asc
|
||||||
|
)
|
||||||
|
|
||||||
|
func (o Order) String() string {
|
||||||
|
str := [...]string{"", "desc", "asc"}
|
||||||
|
return str[o]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Privacy enum specifies the collection privacy returned by WH api
|
||||||
|
type Privacy int
|
||||||
|
|
||||||
|
// Privacy Enum Values
|
||||||
|
const (
|
||||||
|
Private Privacy = iota
|
||||||
|
Public
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p Privacy) String() string {
|
||||||
|
str := [...]string{"private", "public"}
|
||||||
|
return str[p]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TopRange is used to specify the time window for 'top' result when topList is chosen as sort param
|
||||||
|
type TopRange int
|
||||||
|
|
||||||
|
// Enum for TopRange values
|
||||||
|
const (
|
||||||
|
Day TopRange = iota + 1
|
||||||
|
ThreeDay
|
||||||
|
Week
|
||||||
|
Month
|
||||||
|
ThreeMonth
|
||||||
|
SixMonth
|
||||||
|
Year
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t TopRange) String() string {
|
||||||
|
str := [...]string{"1d", "3d", "1w", "1M", "3M", "6M", "1y"}
|
||||||
|
return str[t]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolution specifies the image resolution to find
|
||||||
|
type Resolution struct {
|
||||||
|
Width int64
|
||||||
|
Height int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Resolution) String() string {
|
||||||
|
return fmt.Sprintf("%vx%v", r.Width, r.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Resolution) isValid() bool {
|
||||||
|
return r.Width > 0 && r.Height > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ratio may be used to specify the aspect ratio of the search
|
||||||
|
type Ratio struct {
|
||||||
|
Horizontal int
|
||||||
|
Vertical int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Ratio) String() string {
|
||||||
|
return fmt.Sprintf("%vx%v", r.Horizontal, r.Vertical)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Ratio) isValid() bool {
|
||||||
|
return r.Vertical > 0 && r.Horizontal > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// WallpaperID is a string representing a wallpaper
|
||||||
|
type WallpaperID string
|
||||||
|
|
||||||
|
// Q is used to hold the Q params for various fulltext options that the WH Search supports
|
||||||
|
type Q struct {
|
||||||
|
Tags []string
|
||||||
|
ExcudeTags []string
|
||||||
|
UserName string
|
||||||
|
TagID int
|
||||||
|
Type string // Type is one of png/jpg
|
||||||
|
Like WallpaperID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q Q) toQuery() url.Values {
|
||||||
|
var sb strings.Builder
|
||||||
|
for _, tag := range q.Tags {
|
||||||
|
sb.WriteString("+")
|
||||||
|
sb.WriteString(tag)
|
||||||
|
}
|
||||||
|
for _, etag := range q.ExcudeTags {
|
||||||
|
sb.WriteString("-")
|
||||||
|
sb.WriteString(etag)
|
||||||
|
}
|
||||||
|
if len(q.UserName) > 0 {
|
||||||
|
sb.WriteString("@")
|
||||||
|
sb.WriteString(q.UserName)
|
||||||
|
}
|
||||||
|
if len(q.Type) > 0 {
|
||||||
|
sb.WriteString("type:")
|
||||||
|
sb.WriteString(q.Type)
|
||||||
|
}
|
||||||
|
out := url.Values{}
|
||||||
|
val := sb.String()
|
||||||
|
if len(val) > 0 {
|
||||||
|
out.Set("q", val)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search provides various parameters to search for on wallhaven
|
||||||
|
type Search struct {
|
||||||
|
Query Q
|
||||||
|
Categories string
|
||||||
|
Purities string
|
||||||
|
Sorting Sort
|
||||||
|
Order Order
|
||||||
|
TopRange string
|
||||||
|
AtLeast Resolution
|
||||||
|
Resolutions []Resolution
|
||||||
|
Ratios []Ratio
|
||||||
|
Colors []string // Colors is an array of hex colors represented as strings in #RRGGBB format
|
||||||
|
Page int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Search) toQuery() url.Values {
|
||||||
|
v := s.Query.toQuery()
|
||||||
|
if s.Categories != "" {
|
||||||
|
v.Add("categories", s.Categories)
|
||||||
|
}
|
||||||
|
if s.Purities != "" {
|
||||||
|
v.Add("purity", s.Purities)
|
||||||
|
}
|
||||||
|
if s.Sorting > 0 {
|
||||||
|
v.Add("sorting", s.Sorting.String())
|
||||||
|
}
|
||||||
|
if s.Order > 0 {
|
||||||
|
v.Add("order", s.Order.String())
|
||||||
|
}
|
||||||
|
if s.TopRange != "" && s.Sorting == Toplist {
|
||||||
|
v.Add("topRange", s.TopRange)
|
||||||
|
}
|
||||||
|
if s.AtLeast.isValid() {
|
||||||
|
v.Add("atleast", s.AtLeast.String())
|
||||||
|
}
|
||||||
|
if len(s.Resolutions) > 0 {
|
||||||
|
outRes := []string{}
|
||||||
|
for _, res := range s.Resolutions {
|
||||||
|
if res.isValid() {
|
||||||
|
outRes = append(outRes, res.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(outRes) > 0 {
|
||||||
|
v.Add("resolutions", strings.Join(outRes, ","))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(s.Ratios) > 0 {
|
||||||
|
outRat := []string{}
|
||||||
|
for _, rat := range s.Ratios {
|
||||||
|
if rat.isValid() {
|
||||||
|
outRat = append(outRat, rat.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(outRat) > 0 {
|
||||||
|
v.Add("ratios", strings.Join(outRat, ","))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(s.Colors) > 0 {
|
||||||
|
v.Add("colors", strings.Join([]string(s.Colors), ","))
|
||||||
|
}
|
||||||
|
if s.Page > 0 {
|
||||||
|
v.Add("page", strconv.Itoa(s.Page))
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchWallpapers performs a search on WH given a set of criteria.
|
||||||
|
// Note that this API behaves slightly differently than the various
|
||||||
|
// single item apis as it also includes the metadata for paging purposes
|
||||||
|
func SearchWallpapers(search *Search) (*SearchResults, error) {
|
||||||
|
resp, err := getWithValues("/search/", search.toQuery())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out := &SearchResults{}
|
||||||
|
err = processResponse(resp, out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func processResponse(resp *http.Response, out interface{}) error {
|
||||||
|
byt, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
return json.Unmarshal(byt, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result Structs -- server responses
|
||||||
|
|
||||||
|
// SearchResults a wrapper containing search results from wh
|
||||||
|
type SearchResults struct {
|
||||||
|
Data []Wallpaper `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wallpaper information about a given wallpaper
|
||||||
|
type Wallpaper struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag full data on a given wallpaper tag
|
||||||
|
type Tag struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
CategoryID int `json:"category_id"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
Purity string `json:"purity"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseURL = "https://wallhaven.cc/api/v1"
|
||||||
|
|
||||||
|
func getWithBase(p string) string {
|
||||||
|
return baseURL + p
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWithValues(p string, v url.Values) (*http.Response, error) {
|
||||||
|
u, err := url.Parse(getWithBase(p))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
u.RawQuery = v.Encode()
|
||||||
|
return getAuthedResponse(u.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAuthedResponse(url string) (*http.Response, error) {
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("X-API-Key", os.Getenv("WH_API_KEY"))
|
||||||
|
return client.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
var client = &http.Client{}
|
||||||
|
|
||||||
|
func download(filepath string, resp *http.Response) error {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
out, err := os.Create(filepath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(out, resp.Body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download downloads a wallpaper given the local filepath to save the wallpaper to
|
||||||
|
func (w *Wallpaper) Download(dir string) error {
|
||||||
|
resp, err := getAuthedResponse(w.Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
path := filepath.Join(dir, path.Base(w.Path))
|
||||||
|
return download(path, resp)
|
||||||
|
}
|
BIN
wallhaven_dl
Executable file
BIN
wallhaven_dl
Executable file
Binary file not shown.
Loading…
Reference in New Issue
Block a user