123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114 |
- package anaconda
- import (
- "encoding/json"
- "fmt"
- "io/ioutil"
- "net/http"
- "net/url"
- "strconv"
- "time"
- )
- const (
- //Error code defintions match the Twitter documentation
- //https://developer.twitter.com/en/docs/basics/response-codes
- TwitterErrorCouldNotAuthenticate = 32
- TwitterErrorDoesNotExist = 34
- TwitterErrorAccountSuspended = 64
- TwitterErrorApi1Deprecation = 68 //This should never be needed
- TwitterErrorRateLimitExceeded = 88
- TwitterErrorInvalidToken = 89
- TwitterErrorOverCapacity = 130
- TwitterErrorInternalError = 131
- TwitterErrorCouldNotAuthenticateYou = 135
- TwitterErrorStatusIsADuplicate = 187
- TwitterErrorBadAuthenticationData = 215
- TwitterErrorUserMustVerifyLogin = 231
- // Undocumented by Twitter, but may be returned instead of 34
- TwitterErrorDoesNotExist2 = 144
- )
- type ApiError struct {
- StatusCode int
- Header http.Header
- Body string
- Decoded TwitterErrorResponse
- URL *url.URL
- }
- func newApiError(resp *http.Response) *ApiError {
- // TODO don't ignore this error
- // TODO don't use ReadAll
- p, _ := ioutil.ReadAll(resp.Body)
- var twitterErrorResp TwitterErrorResponse
- _ = json.Unmarshal(p, &twitterErrorResp)
- return &ApiError{
- StatusCode: resp.StatusCode,
- Header: resp.Header,
- Body: string(p),
- Decoded: twitterErrorResp,
- URL: resp.Request.URL,
- }
- }
- // ApiError supports the error interface
- func (aerr ApiError) Error() string {
- return fmt.Sprintf("Get %s returned status %d, %s", aerr.URL, aerr.StatusCode, aerr.Body)
- }
- // Check to see if an error is a Rate Limiting error. If so, find the next available window in the header.
- // Use like so:
- //
- // if aerr, ok := err.(*ApiError); ok {
- // if isRateLimitError, nextWindow := aerr.RateLimitCheck(); isRateLimitError {
- // <-time.After(nextWindow.Sub(time.Now()))
- // }
- // }
- //
- func (aerr *ApiError) RateLimitCheck() (isRateLimitError bool, nextWindow time.Time) {
- // TODO check for error code 130, which also signifies a rate limit
- if aerr.StatusCode == 429 {
- if reset := aerr.Header.Get("X-Rate-Limit-Reset"); reset != "" {
- if resetUnix, err := strconv.ParseInt(reset, 10, 64); err == nil {
- resetTime := time.Unix(resetUnix, 0)
- // Reject any time greater than an hour away
- if resetTime.Sub(time.Now()) > time.Hour {
- return true, time.Now().Add(15 * time.Minute)
- }
- return true, resetTime
- }
- }
- }
- return false, time.Time{}
- }
- //TwitterErrorResponse has an array of Twitter error messages
- //It satisfies the "error" interface
- //For the most part, Twitter seems to return only a single error message
- //Currently, we assume that this always contains exactly one error message
- type TwitterErrorResponse struct {
- Errors []TwitterError `json:"errors"`
- }
- func (tr TwitterErrorResponse) First() error {
- return tr.Errors[0]
- }
- func (tr TwitterErrorResponse) Error() string {
- return tr.Errors[0].Message
- }
- //TwitterError represents a single Twitter error messages/code pair
- type TwitterError struct {
- Message string `json:"message"`
- Code int `json:"code"`
- }
- func (te TwitterError) Error() string {
- return te.Message
- }
|