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