From 052140fb3ccd4b386d8d98ba7355d676c1e0693d Mon Sep 17 00:00:00 2001 From: Andinus Date: Wed, 18 Mar 2020 23:04:21 +0530 Subject: Initial commit for Cetus v0.5.0 --- README.org | 3 + cmd/cetus/main.go | 220 +++++++++++++++++++++++++++++++++++++++++++++++++++++ pkg/apod/json.go | 48 ++++++++++++ pkg/apod/print.go | 19 +++++ pkg/apod/rand.go | 25 ++++++ pkg/bing/bpod.go | 23 ------ pkg/bpod/json.go | 55 ++++++++++++++ pkg/bpod/print.go | 14 ++++ pkg/cetus/cetus.go | 1 - pkg/cetus/req.go | 5 +- pkg/nasa/apod.go | 44 ----------- 11 files changed, 387 insertions(+), 70 deletions(-) create mode 100644 cmd/cetus/main.go create mode 100644 pkg/apod/json.go create mode 100644 pkg/apod/print.go create mode 100644 pkg/apod/rand.go delete mode 100644 pkg/bing/bpod.go create mode 100644 pkg/bpod/json.go create mode 100644 pkg/bpod/print.go delete mode 100644 pkg/nasa/apod.go diff --git a/README.org b/README.org index 6f2bbcc..81d1de7 100644 --- a/README.org +++ b/README.org @@ -25,6 +25,9 @@ doesn't support some desktop environments but should work fine many others. I plan to add support for unsupported DEs too provided they're not hard to implement. +Also the code is very dirty, I couldn't think of a good way to structure it. If +you have a better idea then please let me know. + * Demo I just run some cetus commands on my computer, nothing fancy. I'll make better demo videos someday. diff --git a/cmd/cetus/main.go b/cmd/cetus/main.go new file mode 100644 index 0000000..7629cd2 --- /dev/null +++ b/cmd/cetus/main.go @@ -0,0 +1,220 @@ +package main + +import ( + "flag" + "fmt" + "math/rand" + "os" + "time" + + "framagit.org/andinus/cetus/pkg/apod" + "framagit.org/andinus/cetus/pkg/background" + "framagit.org/andinus/cetus/pkg/bpod" + "framagit.org/andinus/cetus/pkg/cetus" +) + +func main() { + // Early Check: If command was not passed then print usage and + // exit. Later command & service both are checked, this check + // if 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(1) + } + + if os.Args[1] == "version" { + cetus.Version() + os.Exit(0) + } + + // If command & service was not passed then print usage and + // exit. + if len(os.Args) < 3 { + printUsage() + os.Exit(1) + } + + rand.Seed(time.Now().Unix()) + + apodCmd := flag.NewFlagSet("apod", flag.ExitOnError) + defDate := time.Now().UTC().Format("2006-01-02") + + // Flags to parse for apod service. + apodAPI := apodCmd.String("api", "https://api.nasa.gov/planetary/apod", "APOD API link") + apodKey := apodCmd.String("api-key", "DEMO_KEY", "NASA API Key for expanded usage") + apodDate := apodCmd.String("date", defDate, "Date of NASA APOD to retrieve") + apodRand := apodCmd.Bool("random", false, "Choose a date random starting from 1995-06-16") + apodPathOnly := apodCmd.Bool("path-only", false, "Print only the path") + apodQuiet := apodCmd.Bool("quiet", false, "Stay quiet") + apodDump := apodCmd.Bool("dump", false, "Dump received response") + + bpodCmd := flag.NewFlagSet("bpod", flag.ExitOnError) + + // Flags to parse for bpod service. + bpodAPI := bpodCmd.String("api", "https://www.bing.com/HPImageArchive.aspx", "BPOD API") + bpodRand := bpodCmd.Bool("random", false, "Choose a random image from last week's BPOD") + bpodPathOnly := bpodCmd.Bool("path-only", false, "Print only the path") + bpodQuiet := bpodCmd.Bool("quiet", false, "Stay quiet") + bpodDump := bpodCmd.Bool("dump", false, "Dump received response") + + // Switching on commands will cause more repetition than + // switching on service. If we switch on commands then switch + // on service will have to be replicated on every command + // switch. Reverse is also true, this way we will repeat + // command switch in every service but we do so in a better + // way. + // + // However we check if the correct command was passed. version + // command is not included because it has been dealt with + // earlier in the program & the program should've exited after + // that, if it reaches here then it's an error. + switch os.Args[1] { + case "set", "fetch": + default: + fmt.Printf("Invalid command: %q\n", os.Args[1]) + printUsage() + os.Exit(1) + } + + switch os.Args[2] { + case "apod", "nasa": + apodCmd.Parse(os.Args[3:]) + case "bpod", "bing": + bpodCmd.Parse(os.Args[3:]) + default: + fmt.Printf("Invalid service: %q\n", os.Args[2]) + printUsage() + os.Exit(1) + } + + if apodCmd.Parsed() { + // 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"] = string(*apodAPI) + reqInfo["apiKey"] = string(*apodKey) + reqInfo["date"] = string(*apodDate) + + if *apodRand { + reqInfo["apiKey"] = apod.RandDate() + } + + body, err := apod.GetJson(reqInfo) + chkErr(err) + + if *apodDump { + fmt.Printf(body) + os.Exit(0) + } + + res := apod.Res{} + err = apod.UnmarshalJson(&res, body) + chkErr(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) + } + + // If path-only is passed then it will only print the + // path, even if quiet is passed. If the user wants + // the program to be quiet then path-only shouldn't be + // passed. If path-only is not passed & quiet is also + // not passed then print the response. + // + // Path is only printed when the media type is an + // image because res.HDURL is empty on non image media + // type. + if *apodPathOnly { + fmt.Println(res.HDURL) + } else if !*apodQuiet { + apod.Print(res) + } + + // 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. + if res.MediaType == "image" { + err = background.Set(res.HDURL) + chkErr(err) + } + } + + if bpodCmd.Parsed() { + // reqInfo here works similar to apodCmd block's + // reqInfo, refer to explanation there. + reqInfo := make(map[string]string) + reqInfo["api"] = string(*bpodAPI) + + if *bpodRand { + reqInfo["random"] = "true" + } + + body, err := bpod.GetJson(reqInfo) + chkErr(err) + + if *bpodDump { + fmt.Printf(body) + os.Exit(0) + } + + res, err := bpod.UnmarshalJson(body) + chkErr(err) + + // Correct format + res.Url = fmt.Sprintf("%s%s", "https://www.bing.com", res.Url) + dt, err := time.Parse("20060102", res.StartDate) + chkErr(err) + res.StartDate = dt.Format("2006-01-02") + + // path-only here works similar to apodCmd block's + // path-only, refer to explanation there. + if *bpodPathOnly { + fmt.Println(res.Url) + } else if !*bpodQuiet { + bpod.Print(res) + } + + // 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) + } + + err = background.Set(res.Url) + chkErr(err) + } +} + +func printUsage() { + fmt.Println("Usage: cetus []\n") + fmt.Println("Commands: ") + fmt.Println(" set Set the latest image as background") + fmt.Println(" fetch Fetch the latest image information") + fmt.Println(" version Print version") + fmt.Println("Services: ") + fmt.Println(" apod NASA Astronomy Picture of the Day") + fmt.Println(" bpod Bing Photo of the Day") +} + +func chkErr(err error) { + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/pkg/apod/json.go b/pkg/apod/json.go new file mode 100644 index 0000000..4589925 --- /dev/null +++ b/pkg/apod/json.go @@ -0,0 +1,48 @@ +package apod + +import ( + "encoding/json" + "fmt" + "regexp" + + "framagit.org/andinus/cetus/pkg/cetus" +) + +// Res holds the response from the api. +type Res struct { + Copyright string `json:"copyright"` + Date string `json:"date"` + Explanation string `json:"explanation"` + HDURL string `json:"hdurl"` + MediaType string `json:"media_type"` + ServiceVersion string `json:"service_version"` + Title string `json:"title"` + URL string `json:"url"` + + Code int `json:"code"` + Msg string `json:"msg"` +} + +// UnmarshalJson will take body as input & unmarshal it to res +func UnmarshalJson(res *Res, body string) error { + err := json.Unmarshal([]byte(body), res) + if err != nil { + return fmt.Errorf("UnmarshalJson failed\n%s", err.Error()) + } + return nil +} + +// GetJson returns json response received from the api +func GetJson(reqInfo map[string]string) (string, error) { + re := regexp.MustCompile("((19|20)\\d\\d)-(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])") + if !re.MatchString(reqInfo["date"]) { + return "", fmt.Errorf("%s does not match format 'YYYY-MM-DD'", reqInfo["date"]) + } + + params := make(map[string]string) + params["api_key"] = reqInfo["apiKey"] + params["date"] = reqInfo["date"] + + body, err := cetus.GetRes(reqInfo["api"], params) + return string(body), err +} diff --git a/pkg/apod/print.go b/pkg/apod/print.go new file mode 100644 index 0000000..a087665 --- /dev/null +++ b/pkg/apod/print.go @@ -0,0 +1,19 @@ +package apod + +import ( + "fmt" +) + +// Print will print the json output +func Print(res Res) { + fmt.Printf("Title: %s\n\n", res.Title) + 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) +} diff --git a/pkg/apod/rand.go b/pkg/apod/rand.go new file mode 100644 index 0000000..cf92784 --- /dev/null +++ b/pkg/apod/rand.go @@ -0,0 +1,25 @@ +package apod + +import ( + "math/rand" + "time" +) + +// RandDate returns a random date between 1995-06-16 & today +func RandDate() string { + var ( + min int64 + max int64 + sec int64 + delta int64 + date string + ) + min = time.Date(1995, 6, 16, 0, 0, 0, 0, time.UTC).Unix() + max = time.Now().UTC().Unix() + delta = max - min + + sec = rand.Int63n(delta) + min + date = time.Unix(sec, 0).Format("2006-01-02") + + return date +} diff --git a/pkg/bing/bpod.go b/pkg/bing/bpod.go deleted file mode 100644 index c7ee79d..0000000 --- a/pkg/bing/bpod.go +++ /dev/null @@ -1,23 +0,0 @@ -package bing - -import ( - "time" - - "framagit.org/andinus/cetus/pkg/cetus" -) - -// GetBpodJson returns json response received from the api -func GetBpodJson(reqInfo map[string]string, t time.Duration) (string, error) { - params := make(map[string]string) - params["format"] = "js" - params["n"] = "1" - - // if random is true then fetch 7 photos - if reqInfo["random"] == "true" { - params["n"] = "7" - - } - - body, err := cetus.GetRes(reqInfo["api"], params, t) - return string(body), err -} diff --git a/pkg/bpod/json.go b/pkg/bpod/json.go new file mode 100644 index 0000000..4d74668 --- /dev/null +++ b/pkg/bpod/json.go @@ -0,0 +1,55 @@ +package bpod + +import ( + "encoding/json" + "fmt" + "math/rand" + + "framagit.org/andinus/cetus/pkg/cetus" +) + +type Res struct { + StartDate string `json:"startdate"` + FullStartDate string `json:"fullstartdate"` + EndDate string `json:"enddate"` + Url string `json:"url"` + UrlBase string `json:"urlbase"` + Copyright string `json:"copyright"` + CopyrightLink string `json:"copyrightlink"` + Title string `json:"title"` + Hsh string `json:"hsh"` +} + +type List struct { + Photos []Res `json:"images"` +} + +// UnmarshalJson will take body as input & unmarshal it to res +func UnmarshalJson(body string) (Res, error) { + list := List{} + res := Res{} + + err := json.Unmarshal([]byte(body), &list) + if err != nil { + return res, fmt.Errorf("UnmarshalJson failed\n%s", err.Error()) + } + + res = list.Photos[rand.Intn(len(list.Photos))] + return res, nil +} + +// GetJson returns json response received from the api +func GetJson(reqInfo map[string]string) (string, error) { + params := make(map[string]string) + params["format"] = "js" + params["n"] = "1" + + // if random is true then fetch 7 photos + if reqInfo["random"] == "true" { + params["n"] = "7" + + } + + body, err := cetus.GetRes(reqInfo["api"], params) + return string(body), err +} diff --git a/pkg/bpod/print.go b/pkg/bpod/print.go new file mode 100644 index 0000000..75bf948 --- /dev/null +++ b/pkg/bpod/print.go @@ -0,0 +1,14 @@ +package bpod + +import ( + "fmt" +) + +// Print will print the json output +func Print(res Res) { + fmt.Printf("Title: %s\n\n", res.Title) + fmt.Printf("Copyright: %s\n", res.Copyright) + fmt.Printf("Copyright Link: %s\n", res.CopyrightLink) + fmt.Printf("Date: %s\n\n", res.StartDate) + fmt.Printf("URL: %s\n", res.Url) +} diff --git a/pkg/cetus/cetus.go b/pkg/cetus/cetus.go index 17fd906..0e9b7c2 100644 --- a/pkg/cetus/cetus.go +++ b/pkg/cetus/cetus.go @@ -2,7 +2,6 @@ package cetus import ( "fmt" - "log" ) var version string = "v0.5.0" diff --git a/pkg/cetus/req.go b/pkg/cetus/req.go index 84e52a5..ed81374 100644 --- a/pkg/cetus/req.go +++ b/pkg/cetus/req.go @@ -8,9 +8,10 @@ import ( ) // GetRes returns api response -func GetRes(api string, params map[string]string, t time.Duration) (string, error) { +func GetRes(api string, params map[string]string) (string, error) { c := http.Client{ - Timeout: time.Second * t, + // TODO: timeout should be configurable by the user + Timeout: time.Second * 64, } req, err := http.NewRequest(http.MethodGet, api, nil) diff --git a/pkg/nasa/apod.go b/pkg/nasa/apod.go deleted file mode 100644 index 606bbdf..0000000 --- a/pkg/nasa/apod.go +++ /dev/null @@ -1,44 +0,0 @@ -package nasa - -import ( - "fmt" - "math/rand" - "regexp" - "time" - - "framagit.org/andinus/cetus/pkg/cetus" -) - -// RandDate returns a random date between 1995-06-16 & today -func RandDate() string { - var ( - min int64 - max int64 - sec int64 - delta int64 - date string - ) - min = time.Date(1995, 6, 16, 0, 0, 0, 0, time.UTC).Unix() - max = time.Now().UTC().Unix() - delta = max - min - - sec = rand.Int63n(delta) + min - date = time.Unix(sec, 0).Format("2006-01-02") - - return date -} - -// GetApodJson returns json response received from the api -func GetApodJson(reqInfo map[string]string, t time.Duration) (string, error) { - re := regexp.MustCompile("((19|20)\\d\\d)-(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])") - if !re.MatchString(reqInfo["date"]) { - return "", fmt.Errorf("%s does not match format 'YYYY-MM-DD'", reqInfo["date"]) - } - - params := make(map[string]string) - params["api_key"] = reqInfo["apiKey"] - params["date"] = reqInfo["date"] - - body, err := cetus.GetRes(reqInfo["api"], params, t) - return string(body), err -} -- cgit 1.4.1-2-gfad0