~mendelmaleh/xkcd

41f18bbe24b28b9da17264c1faeb1c90ad48c248 — Mendel E 2 years ago
Initial commit
1 files changed, 158 insertions(+), 0 deletions(-)

A xkcd.go
A  => xkcd.go +158 -0
@@ 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
}