This commit is contained in:
abs3nt 2023-10-17 11:33:29 -07:00
commit 22a02d75a5
Signed by: abs3nt
GPG Key ID: FDC6662313FA9386
6 changed files with 511 additions and 0 deletions

19
Makefile Normal file
View 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
View File

@ -0,0 +1,5 @@
module main
go 1.21.3
require github.com/alecthomas/kong v0.8.1

8
go.sum Normal file
View 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
View 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
View 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

Binary file not shown.