141 lines
3.3 KiB
Go
141 lines
3.3 KiB
Go
package anilist
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"seanime/internal/util"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/goccy/go-json"
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
func CustomQuery(body map[string]interface{}, logger *zerolog.Logger, token string) (data interface{}, err error) {
|
|
bodyBytes, err := json.Marshal(body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return customQuery(bodyBytes, logger, token)
|
|
}
|
|
|
|
func customQuery(body []byte, logger *zerolog.Logger, token ...string) (data interface{}, err error) {
|
|
|
|
var rlRemainingStr string
|
|
|
|
reqTime := time.Now()
|
|
defer func() {
|
|
timeSince := time.Since(reqTime)
|
|
formattedDur := timeSince.Truncate(time.Millisecond).String()
|
|
if err != nil {
|
|
logger.Error().Str("duration", formattedDur).Str("rlr", rlRemainingStr).Err(err).Msg("anilist: Failed Request")
|
|
} else {
|
|
if timeSince > 600*time.Millisecond {
|
|
logger.Warn().Str("rtt", formattedDur).Str("rlr", rlRemainingStr).Msg("anilist: Long Request")
|
|
} else {
|
|
logger.Trace().Str("rtt", formattedDur).Str("rlr", rlRemainingStr).Msg("anilist: Successful Request")
|
|
}
|
|
}
|
|
}()
|
|
|
|
defer util.HandlePanicInModuleThen("api/anilist/custom_query", func() {
|
|
err = errors.New("panic in customQuery")
|
|
})
|
|
|
|
client := http.DefaultClient
|
|
|
|
var req *http.Request
|
|
req, err = http.NewRequest("POST", "https://graphql.anilist.co", bytes.NewBuffer(body))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Accept", "application/json")
|
|
if len(token) > 0 && token[0] != "" {
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token[0]))
|
|
}
|
|
|
|
// Send request
|
|
retryCount := 2
|
|
|
|
var resp *http.Response
|
|
for i := 0; i < retryCount; i++ {
|
|
|
|
// Reset response body for retry
|
|
if resp != nil && resp.Body != nil {
|
|
resp.Body.Close()
|
|
}
|
|
|
|
// Recreate the request body if it was read in a previous attempt
|
|
if req.GetBody != nil {
|
|
newBody, err := req.GetBody()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get request body: %w", err)
|
|
}
|
|
req.Body = newBody
|
|
}
|
|
|
|
resp, err = client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
rlRemainingStr = resp.Header.Get("X-Ratelimit-Remaining")
|
|
rlRetryAfterStr := resp.Header.Get("Retry-After")
|
|
rlRetryAfter, err := strconv.Atoi(rlRetryAfterStr)
|
|
if err == nil {
|
|
logger.Warn().Msgf("anilist: Rate limited, retrying in %d seconds", rlRetryAfter+1)
|
|
select {
|
|
case <-time.After(time.Duration(rlRetryAfter+1) * time.Second):
|
|
continue
|
|
}
|
|
}
|
|
|
|
if rlRemainingStr == "" {
|
|
select {
|
|
case <-time.After(5 * time.Second):
|
|
continue
|
|
}
|
|
}
|
|
|
|
break
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
if resp.Header.Get("Content-Encoding") == "gzip" {
|
|
resp.Body, err = gzip.NewReader(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("gzip decode failed: %w", err)
|
|
}
|
|
}
|
|
|
|
var res interface{}
|
|
err = json.NewDecoder(resp.Body).Decode(&res)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
var ok bool
|
|
|
|
reqErrors, ok := res.(map[string]interface{})["errors"].([]interface{})
|
|
|
|
if ok && len(reqErrors) > 0 {
|
|
firstError, foundErr := reqErrors[0].(map[string]interface{})
|
|
if foundErr {
|
|
return nil, errors.New(firstError["message"].(string))
|
|
}
|
|
}
|
|
|
|
data, ok = res.(map[string]interface{})["data"]
|
|
if !ok {
|
|
return nil, errors.New("failed to parse data")
|
|
}
|
|
|
|
return data, nil
|
|
}
|