@@ 1,158 @@
+package xkcd
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "math/rand"
+ "net/http"
+ "strconv"
+ "time"
+)
+
+// XKCD client
+type XKCD struct {
+ BaseURL string
+ Client http.Client
+}
+
+// Comic from xkcd
+type Comic struct {
+ Time time.Time
+ Num int
+ Link string
+ Img string
+ Title string
+ SafeTitle string
+ Alt string
+ Transcript string
+ News string
+}
+
+// APIResponse from xkcd
+type APIResponse struct {
+ Month string `json:"month"`
+ Num int `json:"num"`
+ Link string `json:"link"`
+ Year string `json:"year"`
+ News string `json:"news"`
+ SafeTitle string `json:"safe_title"`
+ Transcript string `json:"transcript"`
+ Alt string `json:"alt"`
+ Img string `json:"img"`
+ Title string `json:"title"`
+ Day string `json:"day"`
+}
+
+// New returns an instance of the xkcd client.
+func New() *XKCD {
+ return &XKCD{
+ BaseURL: "https://xkcd.com/",
+ Client: *http.DefaultClient,
+ }
+}
+
+// Get a comic from string. "0" or "" will return the latest comic.
+func (x *XKCD) Get(query string) (Comic, error) {
+ if query == "" || query == "0" {
+ return x.GetNum(0)
+ }
+
+ var c Comic
+ if isDigits(query) {
+ n, err := strconv.Atoi(query)
+ if err != nil {
+ return c, err
+ }
+
+ return x.GetNum(n)
+ }
+
+ return c, fmt.Errorf("xkcd: unable to get any comics for %q", query)
+}
+
+// GetRandom xkcd comic
+func (x *XKCD) GetRandom() (c Comic, err error) {
+ latest, err := x.GetNum(0)
+ if err != nil {
+ return
+ }
+
+ rand.Seed(time.Now().UnixNano())
+ return x.GetNum(rand.Intn(latest.Num))
+}
+
+// GetNum gets the xkcd comic by number
+func (x *XKCD) GetNum(n int) (Comic, error) {
+ s := "info.0.json"
+
+ if n != 0 {
+ s = strconv.Itoa(n) + "/" + s
+ }
+
+ return x.Query(s)
+}
+
+// Query API with path
+func (x *XKCD) Query(path string) (c Comic, err error) {
+ resp, err := x.Client.Get(x.BaseURL + path)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return
+ }
+
+ err = json.Unmarshal(body, &c)
+ return
+}
+
+// URL returns the URL of this comic.
+func (c *Comic) URL(x *XKCD) string {
+ return x.BaseURL + strconv.Itoa(c.Num)
+}
+
+// JSONURL returns the URL of the JSON of this comic.
+func (c *Comic) JSONURL(x *XKCD) string {
+ return x.BaseURL + strconv.Itoa(c.Num) + "/info.0.json"
+}
+
+// UnmarshalJSON into a proper Comic struct.
+func (c *Comic) UnmarshalJSON(b []byte) error {
+ r := &APIResponse{}
+ if err := json.Unmarshal(b, r); err != nil {
+ return err
+ }
+
+ t, err := time.Parse("1-2-2006", fmt.Sprintf("%s-%s-%s", r.Month, r.Day, r.Year))
+ if err != nil {
+ return err
+ }
+
+ *c = Comic{
+ Time: t,
+ Num: r.Num,
+ Link: r.Link,
+ Img: r.Img,
+ Title: r.Title,
+ SafeTitle: r.SafeTitle,
+ Alt: r.Alt,
+ Transcript: r.Transcript,
+ News: r.News,
+ }
+
+ return nil
+}
+
+func isDigits(s string) bool {
+ for _, r := range s {
+ if r < '0' || '9' < r {
+ return false
+ }
+ }
+
+ return true
+}