From ff9f4f328619377f5035c4835ff8c587f4661fc4 Mon Sep 17 00:00:00 2001 From: rtz12 Date: Sun, 13 Nov 2016 03:45:39 +0100 Subject: Go Anilist-API wurde implementiert diff --git a/goanilist.go b/goanilist.go new file mode 100644 index 0000000..54cdaf8 --- /dev/null +++ b/goanilist.go @@ -0,0 +1,172 @@ +package goanilist + +import ( + "database/sql" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "time" +) + +const API_URL = "https://anilist.co/api/" + +var ( + ErrAnilistCoupled = errors.New("Anilist is already coupled!") + ErrAnilistNotCoupled = errors.New("Anilist is not coupled") +) + +var httpClient = &http.Client{} + +type Client struct { + db *sql.DB + user int + id string + secret string + token string + refreshToken string + tokenExpires int64 + IsCoupled bool +} + +func NewClient(db *sql.DB, user int, id, secret string) *Client { + c := &Client{ + db: db, + user: user, + id: id, + secret: secret, + } + err := db.QueryRow("SELECT token, refresh_token FROM login.anilist_api_tokens WHERE user_id = $1;", user).Scan(&c.token, &c.refreshToken) + c.IsCoupled = err == nil + return c +} + +func (c *Client) AuthorizeURL() string { + return fmt.Sprintf("%sauth/authorize?grant_type=%s&client_id=%s&response_type=%s", + API_URL, + "authorization_pin", + url.QueryEscape(c.id), + "pin") +} + +func (c *Client) NeedsToRenew() bool { + t := time.Now().UTC().Unix() + return t > (c.tokenExpires - 60) //60 seconds of buffer time +} + +func (c *Client) CoupleByPin(pin string) error { + if c.IsCoupled { + return ErrAnilistCoupled + } + res := AccessTokenResult{} + err := c.post("auth/access_token", url.Values{ + "grant_type": {"authorization_pin"}, + "client_id": {c.id}, + "client_secret": {c.secret}, + "code": {pin}, + }, &res) + switch res.Error { + case "invalid_request": + err = errors.New("Der eingegebene Code ist nicht korrekt. Bitte prüfe, ob du ihn richtig kopiert hast.") + } + if err != nil { + return err + } + _, err = c.db.Exec("INSERT INTO login.anilist_api_tokens (user_id, token, refresh_token) VALUES ($1, $2, $3);", c.user, res.AccessToken, res.RefreshToken) + if err != nil { + return err + } + c.token = res.AccessToken + c.refreshToken = res.RefreshToken + c.tokenExpires = res.Expires + return nil +} + +func (c *Client) RenewToken() error { + if !c.IsCoupled { + return ErrAnilistNotCoupled + } + log.Println("Token expired, get new token..") + res := AccessTokenResult{} + err := c.post("auth/access_token", url.Values{ + "grant_type": {"refresh_token"}, + "client_id": {c.id}, + "client_secret": {c.secret}, + "refresh_token": {c.refreshToken}, + }, &res) + if err != nil { + return err + } + _, err = c.db.Exec("UPDATE login.anilist_api_tokens SET token = $1 WHERE user_id = $2;", res.AccessToken, c.user) + if err != nil { + return err + } + c.token = res.AccessToken + c.tokenExpires = res.Expires + return nil +} + +func (c *Client) User() (*UserResult, error) { + if !c.IsCoupled { + return nil, ErrAnilistNotCoupled + } + if c.NeedsToRenew() { + if err := c.RenewToken(); err != nil { + return nil, err + } + } + res := &UserResult{} + err := c.get("user", res) + if err != nil { + return nil, err + } + return res, nil +} + +func (c *Client) post(path string, values url.Values, result interface{}) error { + resp, err := http.PostForm(fmt.Sprintf("%s%s", API_URL, path), values) + if err != nil { + return err + } + return c.handleResponse(resp, path, values, result) +} + +func (c *Client) get(path string, result interface{}) error { + req, err := http.NewRequest("GET", fmt.Sprintf("%s%s", API_URL, path), nil) + if err != nil { + return err + } + req.Header.Add("Authorization", "Bearer "+c.token) + resp, err := httpClient.Do(req) + if err != nil { + return err + } + return c.handleResponse(resp, path, url.Values{}, result) +} + +func (c *Client) handleResponse(resp *http.Response, path string, values url.Values, result interface{}) error { + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + err = json.Unmarshal(body, result) + if err != nil { + log.Println(string(body)) + return err + } + resErr := result.(APIError).Get() + resErr.ParseRawError() + if resp.StatusCode != 200 { + log.Printf("%s: %v -> %s\n", path, values, resp.Status) + if resErr.Error != "" { + log.Printf("%v\n", resErr) + return errors.New(fmt.Sprintf("%s: %s", resErr.Error, resErr.ErrorMessage)) + } else { + return errors.New(resp.Status) + } + } + return nil +} diff --git a/types.go b/types.go new file mode 100644 index 0000000..e451806 --- /dev/null +++ b/types.go @@ -0,0 +1,48 @@ +package goanilist + +import "encoding/json" + +type APIError interface { + Get() *FlexibleError +} + +type FlexibleError struct { + RawError json.RawMessage `json:"error"` + Error string + ErrorMessage string `json:"error_message"` +} + +type complexError struct { + Error string `json:"error"` + ErrorDescription string `json:"error_description"` +} + +func (e *FlexibleError) ParseRawError() { + ce := complexError{} + err := json.Unmarshal(e.RawError, &ce) + if err == nil { + e.Error = ce.Error + e.ErrorMessage = ce.ErrorDescription + } else { + e.Error = string(e.RawError) + } +} + +func (e *FlexibleError) Get() *FlexibleError { + return e +} + +type AccessTokenResult struct { + FlexibleError + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + Expires int64 `json:"expires"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` +} + +type UserResult struct { + FlexibleError + ID int `json:"id"` + DisplayName string `json:"display_name"` +} -- cgit v0.10.1