Add apod support & fix errors
This commit is contained in:
parent
28e35c0b0b
commit
3ee3715ae7
|
@ -5,7 +5,7 @@ import (
|
|||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"framagit.org/andinus/cetus/pkg/request"
|
||||
"tildegit.org/andinus/cetus/request"
|
||||
)
|
||||
|
||||
// APOD holds the response from the api. Not every field is returned
|
||||
|
@ -27,7 +27,7 @@ type APOD struct {
|
|||
}
|
||||
|
||||
// UnmarshalJson will take body as input & unmarshal it to res.
|
||||
func UnmarshalJson(res *Res, body string) error {
|
||||
func UnmarshalJson(res *APOD, body string) error {
|
||||
err := json.Unmarshal([]byte(body), res)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("json.go: unmarshalling json failed\n%s",
|
||||
|
|
|
@ -10,13 +10,13 @@ import (
|
|||
// GetDir returns cetus cache directory. Default cache directory on
|
||||
// macOS is $HOME/Library/Caches.
|
||||
func GetDir() string {
|
||||
cacheDir = fmt.Sprintf("%s/%s/%s",
|
||||
cacheDir := fmt.Sprintf("%s/%s/%s",
|
||||
os.Getenv("HOME"),
|
||||
"Library",
|
||||
"Caches")
|
||||
|
||||
// Cetus cache directory is cacheDir/cetus
|
||||
cetusCacheDir = fmt.Sprintf("%s/%s", cacheDir,
|
||||
cetusCacheDir := fmt.Sprintf("%s/%s", cacheDir,
|
||||
"cetus")
|
||||
|
||||
return cetusCacheDir
|
||||
|
|
|
@ -22,7 +22,7 @@ func GetDir() string {
|
|||
}
|
||||
|
||||
// Cetus cache directory is cacheDir/cetus.
|
||||
cetusCacheDir = fmt.Sprintf("%s/%s", cacheDir,
|
||||
cetusCacheDir := fmt.Sprintf("%s/%s", cacheDir,
|
||||
"cetus")
|
||||
|
||||
return cetusCacheDir
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"tildegit.org/andinus/cetus/apod"
|
||||
"tildegit.org/andinus/cetus/background"
|
||||
"tildegit.org/andinus/cetus/cache"
|
||||
"tildegit.org/andinus/cetus/notification"
|
||||
)
|
||||
|
||||
var (
|
||||
err error
|
||||
body string
|
||||
file string
|
||||
reqInfo map[string]string
|
||||
)
|
||||
|
||||
func execAPOD() {
|
||||
apodApi := getEnv("APOD_API", "https://api.nasa.gov/planetary/apod")
|
||||
apodKey := getEnv("APOD_KEY", "DEMO_KEY")
|
||||
|
||||
// reqInfo holds all the parameters that needs to be sent with
|
||||
// the request. GetJson() will pack apiKey & date in params
|
||||
// map before sending it to another function. Adding params
|
||||
// here will not change the behaviour of the function, changes
|
||||
// have to be made in GetJson() too.
|
||||
reqInfo = make(map[string]string)
|
||||
reqInfo["api"] = apodApi
|
||||
reqInfo["apiKey"] = apodKey
|
||||
reqInfo["date"] = apodDate
|
||||
|
||||
if random {
|
||||
reqInfo["date"] = apod.RandDate()
|
||||
}
|
||||
|
||||
cacheDir := fmt.Sprintf("%s/%s", cache.GetDir(), "apod")
|
||||
os.MkdirAll(cacheDir, os.ModePerm)
|
||||
|
||||
// Check if the file is available locally, if it is then don't
|
||||
// download it again and get it from disk
|
||||
file = fmt.Sprintf("%s/%s.json", cacheDir, reqInfo["date"])
|
||||
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
|
||||
// Not being able to read from the cache file is a
|
||||
// small error and the program shouldn't exit but
|
||||
// should continue after printing the log so that the
|
||||
// user can investigate it later.
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s%s\n%s",
|
||||
"apod.go: failed to read file to data: ", file,
|
||||
err.Error())
|
||||
log.Println(err)
|
||||
dlAndCacheBody()
|
||||
}
|
||||
body = string(data)
|
||||
|
||||
} else if os.IsNotExist(err) {
|
||||
dlAndCacheBody()
|
||||
|
||||
} else {
|
||||
// If file existed then that is handled by the if
|
||||
// block, if it didn't exist then that is handled by
|
||||
// the else if block. If we reach here then that means
|
||||
// it's Schrödinger's file & something else went
|
||||
// wrong.
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if dump {
|
||||
fmt.Printf(body)
|
||||
}
|
||||
|
||||
res := apod.APOD{}
|
||||
err = apod.UnmarshalJson(&res, body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// res.Msg will be returned when there is error on user input
|
||||
// or the api server.
|
||||
if len(res.Msg) != 0 {
|
||||
fmt.Printf("Message: %s", res.Msg)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Send a desktop notification if notify flag was passed.
|
||||
if notify {
|
||||
n := notification.Notif{}
|
||||
n.Title = res.Title
|
||||
n.Message = fmt.Sprintf("%s\n\n%s",
|
||||
res.Date,
|
||||
res.Explanation)
|
||||
|
||||
err = n.Notify()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
if print {
|
||||
fmt.Printf("Title: %s\n\n", res.Title)
|
||||
if len(res.Copyright) != 0 {
|
||||
fmt.Printf("Copyright: %s\n", res.Copyright)
|
||||
}
|
||||
fmt.Printf("Date: %s\n\n", res.Date)
|
||||
fmt.Printf("Media Type: %s\n", res.MediaType)
|
||||
if res.MediaType == "image" {
|
||||
fmt.Printf("URL: %s\n\n", res.HDURL)
|
||||
} else {
|
||||
fmt.Printf("URL: %s\n\n", res.URL)
|
||||
}
|
||||
fmt.Printf("Explanation: %s\n", res.Explanation)
|
||||
}
|
||||
|
||||
// Proceed only if the command was set because if it was fetch
|
||||
// then it's already finished & should exit now.
|
||||
if os.Args[1] == "fetch" {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Try to set background only if the media type is an image.
|
||||
// First it downloads the image to the cache directory and
|
||||
// then tries to set it with feh. If the download fails then
|
||||
// it exits with a non-zero exit code.
|
||||
if res.MediaType != "image" {
|
||||
os.Exit(0)
|
||||
}
|
||||
imgFile := fmt.Sprintf("%s/%s", cacheDir, res.Title)
|
||||
|
||||
// Check if the file is available locally, if it is then don't
|
||||
// download it again and set it from disk.
|
||||
if _, err := os.Stat(imgFile); os.IsNotExist(err) {
|
||||
err = background.Download(imgFile, res.HDURL)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
err = background.SetFromFile(imgFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func dlAndCacheBody() {
|
||||
body, err = apod.GetJson(reqInfo)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s\n%s",
|
||||
"apod.go: failed to get json response from api",
|
||||
err.Error())
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Write body to the cache so that it can be read later.
|
||||
err = ioutil.WriteFile(file, []byte(body), 0644)
|
||||
|
||||
// Not being able to write to the cache file is a small error
|
||||
// and the program shouldn't exit but should continue after
|
||||
// printing the log so that the user can investigate it later.
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s\n%s",
|
||||
"apod.go: failed to write body to file: ", file,
|
||||
err.Error())
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package main
|
||||
|
||||
import "os"
|
||||
|
||||
// getEnv will check if the the key exists, if it does then it'll
|
||||
// return the value otherwise it will return fallback string.
|
||||
func getEnv(key, fallback string) string {
|
||||
// We use os.LookupEnv instead of using os.GetEnv and checking
|
||||
// if the length equals 0 because environment variable can be
|
||||
// set and be of length 0. User could've set key="" which
|
||||
// means the variable was set but the length is 0. There is no
|
||||
// reason why user would want to do this over here though.
|
||||
value, exists := os.LookupEnv(key)
|
||||
if !exists {
|
||||
value = fallback
|
||||
}
|
||||
return value
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
version string = "v0.6.0"
|
||||
dump bool
|
||||
random bool
|
||||
notify bool
|
||||
print bool
|
||||
|
||||
apodDate string
|
||||
// bpodApi string = getEnv("BPOD_API", "https://www.bing.com/HPImageArchive.aspx")
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// Early Check: If command was not passed then print usage and
|
||||
// exit. Later command & service both are checked, this check
|
||||
// is for version command. If not checked then running cetus
|
||||
// without any args will fail because os.Args[1] will panic
|
||||
// the program & produce runtime error.
|
||||
if len(os.Args) == 1 {
|
||||
printUsage()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
parseArgs()
|
||||
}
|
||||
|
||||
// parseArgs will be parsing the arguments, it will verify if they are
|
||||
// correct. Flag values are also set by parseArgs.
|
||||
func parseArgs() {
|
||||
// Running just `cetus` would've paniced the program if length
|
||||
// of os.Args was not checked beforehand because there would
|
||||
// be no os.Args[1].
|
||||
switch os.Args[1] {
|
||||
case "version", "-version", "--version", "-v":
|
||||
fmt.Printf("Cetus %s\n", version)
|
||||
os.Exit(0)
|
||||
|
||||
case "help", "-help", "--help", "-h":
|
||||
// If help was passed then the program shouldn't exit
|
||||
// with non-zero error code.
|
||||
printUsage()
|
||||
os.Exit(0)
|
||||
|
||||
case "set", "fetch":
|
||||
// If command & service was not passed then print
|
||||
// usage and exit.
|
||||
if len(os.Args) < 3 {
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Printf("Invalid command: %q\n", os.Args[1])
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
rand.Seed(time.Now().Unix())
|
||||
|
||||
// If the program has reached this far then that means a valid
|
||||
// command was passed & now we should check if a valid service
|
||||
// was passed and parse the flags.
|
||||
cetus := flag.NewFlagSet("cetus", flag.ExitOnError)
|
||||
|
||||
// We first declare common flags then service specific flags.
|
||||
cetus.BoolVar(&dump, "dump", false, "Dump the response")
|
||||
cetus.BoolVar(¬ify, "notify", false, "Send a desktop notification with info")
|
||||
cetus.BoolVar(&print, "print", false, "Print information")
|
||||
cetus.BoolVar(&random, "random", false, "Choose a random image")
|
||||
|
||||
switch os.Args[2] {
|
||||
case "apod", "nasa":
|
||||
defDate := time.Now().UTC().Format("2006-01-02")
|
||||
cetus.StringVar(&apodDate, "date", defDate, "Date of NASA APOD to retrieve")
|
||||
cetus.Parse(os.Args[3:])
|
||||
|
||||
execAPOD()
|
||||
// case "bpod", "bing":
|
||||
// parseFlags()
|
||||
// execBPOD()
|
||||
default:
|
||||
fmt.Printf("Invalid service: %q\n", os.Args[2])
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func printUsage() {
|
||||
fmt.Println("Usage: cetus <command> <service> [<flags>]")
|
||||
fmt.Println("\nCommands: ")
|
||||
fmt.Println(" set Set the background")
|
||||
fmt.Println(" fetch Fetch the response only")
|
||||
fmt.Println(" help Print help")
|
||||
fmt.Println(" version Print Cetus version")
|
||||
fmt.Println("\nServices: ")
|
||||
fmt.Println(" apod NASA Astronomy Picture of the Day")
|
||||
fmt.Println(" bpod Bing Photo of the Day")
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package notification
|
||||
|
||||
// Notif struct holds information about the notification. Other
|
||||
// parameters like urgency & timeout could be added when required.
|
||||
type Notif struct {
|
||||
Title string
|
||||
Message string
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// +build darwin
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Notify sends a desktop notification to the user using osascript. It
|
||||
// handles information in the form of Notif struct. It returns an
|
||||
// error (if exists).
|
||||
func (n Notif) Notify() error {
|
||||
// This script cuts out parts of notification, this bug was
|
||||
// confirmed on macOS Catalina 10.15.3, fix not yet known.
|
||||
err := exec.Command("osascript", "-e",
|
||||
`display notification `+strconv.Quote(n.Message)+` with title `+strconv.Quote(n.Title)).Run()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s\n%s",
|
||||
"notify_darwin.go: failed to sent notification with osascript",
|
||||
err.Error())
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// +build linux netbsd openbsd freebsd dragonfly
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Notify sends a desktop notification to the user using libnotify. It
|
||||
// handles information in the form of Notif struct. It returns an
|
||||
// error (if exists).
|
||||
func (n Notif) Notify() error {
|
||||
err := exec.Command("notify-send", n.Title, n.Message).Run()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s\n%s",
|
||||
"notify_unix.go: failed to sent notification with notify-send",
|
||||
err.Error())
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -10,9 +10,14 @@ import (
|
|||
|
||||
// GetRes takes api and params as input and returns the body and
|
||||
// error.
|
||||
func (c http.Client) GetRes(api string, params map[string]string) (string, error) {
|
||||
func GetRes(api string, params map[string]string) (string, error) {
|
||||
var body string
|
||||
|
||||
c := http.Client{
|
||||
// TODO: timeout should be configurable by the user
|
||||
Timeout: time.Second * 64,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, api, nil)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s\n%s",
|
Loading…
Reference in New Issue