-
Notifications
You must be signed in to change notification settings - Fork 3
/
apod.go
144 lines (127 loc) · 3.19 KB
/
apod.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package nasa
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"net/url"
"os"
"sync"
"time"
)
var nasaKey = os.Getenv("NASAKEY")
func init() {
if nasaKey == "" {
nasaKey = "DEMO_KEY"
}
}
// APIKEYMissing is the message displayed if a custom API Key is not defined
const APIKEYMissing = `Currently using the demo API Key DEMO_KEY. Apply for an API key at https://api.nasa.gov/index.html#apply-for-an-api-key
Check out https://github.com/peteretelej/nasa/blob/master/README.md for more info.
`
// APODEndpoint is the NASA API APOD endpoint
var APODEndpoint = "https://api.nasa.gov/planetary/apod"
// Image defines the structure of NASA images
type Image struct {
Date string `json:"date"`
Title string `json:"title"`
URL string `json:"url"`
HDURL string `json:"hdurl"`
Explanation string `json:"explanation"`
ApodDate time.Time `json:",omitempty"`
}
func (ni Image) String() string {
return fmt.Sprintf(`Title: %s
Date: %s
Image: %s
HD Image: %s
About:
%s
`, ni.Title, ni.Date, ni.URL, ni.HDURL, ni.Explanation)
}
func init() {
rand.Seed(time.Now().UnixNano())
}
// RandomAPOD returns an Astronomy Picture of the Day based on a random date
// Picks any image shared between the last 2 years
func RandomAPOD() (*Image, error) {
days := 2 * 365 // Any day in last 2 years
randDaysOld := time.Duration(rand.Intn(days))
t := time.Now().Add(-(time.Hour * 24 * randDaysOld))
return ApodImage(t)
}
// caches todays APOD
type todaysAPOD struct {
mu sync.RWMutex // protects the following
date string // YYYY-MM-DD
apod *Image
}
var tAPOD = &todaysAPOD{}
func (v *todaysAPOD) update(apod Image) {
v.mu.Lock()
v.apod = &apod
v.date = apod.Date
v.mu.Unlock()
}
//APODToday returns today's APOD, from cache if possible, fetches fresh if not
func APODToday() (*Image, error) {
d := time.Now().Format("2006-01-02")
tAPOD.mu.RLock()
cacheddate, apod := tAPOD.date, tAPOD.apod
tAPOD.mu.RUnlock()
if cacheddate != d || apod == nil {
return ApodImage(time.Now())
}
return apod, nil
}
// ApodImage returns the NASA Astronomy Picture of the Day
func ApodImage(t time.Time) (*Image, error) {
var today bool
if t.After(time.Now()) {
t = time.Now()
}
date := t.Format("2006-01-02")
today = time.Now().Format("2006-01-02") == date
u, err := url.Parse(APODEndpoint)
if err != nil {
return nil, err
}
q := u.Query()
q.Set("api_key", nasaKey)
if !today {
q.Add("date", date)
}
u.RawQuery = q.Encode()
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return nil, err
}
client := &http.Client{Timeout: time.Second * 20}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("unable to connect to NASA API, %v", err)
}
defer func() { _ = resp.Body.Close() }()
dat, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
_ = resp.Body.Close()
var ni Image
err = json.Unmarshal(dat, &ni)
if err != nil {
return nil, err
}
if ni.URL == "" && ni.HDURL == "" {
return nil, errors.New("NASA APOD API is returned an invalid response, may be down temporarily")
}
if t, err := time.Parse("2006-01-02", ni.Date); err == nil {
ni.ApodDate = t
}
if today {
tAPOD.update(ni)
}
return &ni, nil
}