feature: implement most AbuseIPDB endpoints
* implement all but report-bulk endpoint * basic tests done
This commit is contained in:
commit
1009e032e4
4 changed files with 539 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.idea
|
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module go-abuseipdb
|
||||||
|
|
||||||
|
go 1.22
|
484
pkg/abuseipdb/client.go
Normal file
484
pkg/abuseipdb/client.go
Normal file
|
@ -0,0 +1,484 @@
|
||||||
|
package abuseipdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"go-abuseipdb/pkg/abuseipdb/rate"
|
||||||
|
"html"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Version = "v2.0"
|
||||||
|
defaultBaseURL = "https://api.abuseipdb.com/api/v2/"
|
||||||
|
defaultUserAgent = "go-abuseipdb/" + Version
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
client *http.Client
|
||||||
|
BaseURL *url.URL
|
||||||
|
|
||||||
|
UserAgent string
|
||||||
|
APIKey string
|
||||||
|
|
||||||
|
// ratelimit
|
||||||
|
RateLimit *rate.Limit
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(client *http.Client) *Client {
|
||||||
|
if client == nil {
|
||||||
|
client = &http.Client{}
|
||||||
|
}
|
||||||
|
copiedClient := *client
|
||||||
|
c := &Client{client: &copiedClient}
|
||||||
|
c.initialize()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) AddApiKey(key string) {
|
||||||
|
c.APIKey = key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) initialize() {
|
||||||
|
if c.client == nil {
|
||||||
|
c.client = &http.Client{}
|
||||||
|
}
|
||||||
|
if c.BaseURL == nil {
|
||||||
|
c.BaseURL, _ = url.Parse(defaultBaseURL)
|
||||||
|
}
|
||||||
|
if c.UserAgent == "" {
|
||||||
|
c.UserAgent = defaultUserAgent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) NewRequest(method, urlStr string, parameter map[string]string) (*http.Request, error) {
|
||||||
|
if !strings.HasSuffix(c.BaseURL.Path, "/") {
|
||||||
|
return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := c.BaseURL.Parse(urlStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, u.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("User-Agent", c.UserAgent)
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
req.Header.Set("Key", c.APIKey)
|
||||||
|
|
||||||
|
q := req.URL.Query()
|
||||||
|
for k, v := range parameter {
|
||||||
|
q.Add(k, v)
|
||||||
|
}
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorResponse struct {
|
||||||
|
Errors []struct {
|
||||||
|
Detail string `json:"detail"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
} `json:"errors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleError(r *http.Response) error {
|
||||||
|
if r.StatusCode == http.StatusTooManyRequests {
|
||||||
|
return fmt.Errorf("too many requests, see: Client.RateLimit")
|
||||||
|
}
|
||||||
|
if r.StatusCode >= 400 {
|
||||||
|
var errR *errorResponse
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&errR)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to decode error message %d: %w", r.StatusCode, err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("abuseipdb error: %s", errR.Errors[0].Detail)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Do(ctx context.Context, req *http.Request, v any) error {
|
||||||
|
resp, err := c.client.Do(req.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
switch v := v.(type) {
|
||||||
|
case nil:
|
||||||
|
case io.Writer:
|
||||||
|
_, err = io.Copy(v, resp.Body)
|
||||||
|
default:
|
||||||
|
decErr := json.NewDecoder(resp.Body).Decode(v)
|
||||||
|
if decErr == io.EOF {
|
||||||
|
decErr = nil // ignore EOF errors caused by empty response body
|
||||||
|
}
|
||||||
|
if decErr != nil {
|
||||||
|
err = decErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckOptions struct {
|
||||||
|
MaxAgeInDays int `json:"maxAgeInDays,omitempty"`
|
||||||
|
Verbose bool `json:"verbose,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckResult struct {
|
||||||
|
Data struct {
|
||||||
|
IpAddress string `json:"ipAddress"`
|
||||||
|
IsPublic bool `json:"isPublic"`
|
||||||
|
IpVersion int `json:"ipVersion"`
|
||||||
|
IsWhitelisted bool `json:"isWhitelisted"`
|
||||||
|
AbuseConfidenceScore int `json:"abuseConfidenceScore"` // 100 spam, 0 ham
|
||||||
|
CountryCode string `json:"countryCode"`
|
||||||
|
CountryName string `json:"countryName,omitempty"`
|
||||||
|
UsageType string `json:"usageType"`
|
||||||
|
Isp string `json:"isp"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
Hostnames []string `json:"hostnames"`
|
||||||
|
IsTor bool `json:"isTor"`
|
||||||
|
TotalReports int `json:"totalReports"`
|
||||||
|
NumDistinctUsers int `json:"numDistinctUsers"`
|
||||||
|
LastReportedAt time.Time `json:"lastReportedAt"`
|
||||||
|
Reports []CheckResultReport `json:"reports,omitempty"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckResultReport struct {
|
||||||
|
ReportedAt time.Time `json:"reportedAt"`
|
||||||
|
Comment string `json:"comment"`
|
||||||
|
Categories []int `json:"categories"`
|
||||||
|
ReporterId int `json:"reporterId"`
|
||||||
|
ReporterCountryCode string `json:"reporterCountryCode"`
|
||||||
|
ReporterCountryName string `json:"reporterCountryName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Check(ctx context.Context, ip net.IP, opts *CheckOptions) (*CheckResult, error) {
|
||||||
|
var endpoint = "check"
|
||||||
|
ipAddress := html.EscapeString(ip.String())
|
||||||
|
parameters := map[string]string{
|
||||||
|
"ipAddress": ipAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts != nil {
|
||||||
|
if opts.MaxAgeInDays > 0 && opts.MaxAgeInDays <= 365 {
|
||||||
|
parameters["maxAgeInDays"] = strconv.Itoa(opts.MaxAgeInDays)
|
||||||
|
}
|
||||||
|
if opts.Verbose {
|
||||||
|
parameters["verbose"] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := c.NewRequest(http.MethodGet, endpoint, parameters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result *CheckResult
|
||||||
|
err = c.Do(ctx, req, result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportsOptions struct {
|
||||||
|
MaxAgeInDays int `json:"maxAgeInDays,omitempty"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PerPage int `json:"perPage,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportsResult struct {
|
||||||
|
Data struct {
|
||||||
|
Total int `json:"total"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
Count int `json:"count"`
|
||||||
|
PerPage int `json:"perPage"`
|
||||||
|
LastPage int `json:"lastPage"`
|
||||||
|
NextPageUrl string `json:"nextPageUrl"`
|
||||||
|
PreviousPageUrl string `json:"previousPageUrl"`
|
||||||
|
Results []ReportResultReport `json:"results,omitempty"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
type ReportResultReport struct {
|
||||||
|
ReportedAt time.Time `json:"reportedAt"`
|
||||||
|
Comment string `json:"comment"`
|
||||||
|
Categories []int `json:"categories"`
|
||||||
|
ReporterId int `json:"reporterId"`
|
||||||
|
ReporterCountryCode string `json:"reporterCountryCode"`
|
||||||
|
ReporterCountryName string `json:"reporterCountryName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Reports(ctx context.Context, ip net.IP, opts *ReportsOptions) (*ReportsResult, error) {
|
||||||
|
var endpoint = "reports"
|
||||||
|
ipAddress := html.EscapeString(ip.String())
|
||||||
|
parameters := map[string]string{
|
||||||
|
"ipAddress": ipAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts != nil {
|
||||||
|
if opts.MaxAgeInDays > 0 && opts.MaxAgeInDays <= 365 {
|
||||||
|
parameters["maxAgeInDays"] = strconv.Itoa(opts.MaxAgeInDays)
|
||||||
|
}
|
||||||
|
if opts.Page > 0 {
|
||||||
|
parameters["page"] = strconv.Itoa(opts.Page)
|
||||||
|
}
|
||||||
|
if opts.PerPage > 0 && opts.PerPage <= 100 {
|
||||||
|
parameters["perPage"] = strconv.Itoa(opts.PerPage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := c.NewRequest(http.MethodGet, endpoint, parameters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result *ReportsResult
|
||||||
|
err = c.Do(ctx, req, result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlacklistOptions struct {
|
||||||
|
ConfidenceMinimum int `json:"confidenceMinimum,omitempty"`
|
||||||
|
Limit int `json:"limit,omitempty"`
|
||||||
|
OnlyCountries []string `json:"onlyCountries,omitempty"`
|
||||||
|
ExceptCountries []string `json:"exceptCountries,omitempty"`
|
||||||
|
IpVersion int `json:"ipVersion,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlacklistResult struct {
|
||||||
|
Meta struct {
|
||||||
|
GeneratedAt time.Time `json:"generatedAt"`
|
||||||
|
} `json:"meta"`
|
||||||
|
Data []struct {
|
||||||
|
IpAddress string `json:"ipAddress"`
|
||||||
|
AbuseConfidenceScore int `json:"abuseConfidenceScore,omitempty"`
|
||||||
|
LastReportedAt time.Time `json:"lastReportedAt"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleBlacklistOptions(opts *BlacklistOptions) map[string]string {
|
||||||
|
parameters := map[string]string{}
|
||||||
|
if opts != nil {
|
||||||
|
if opts.ConfidenceMinimum > 25 && opts.ConfidenceMinimum <= 100 {
|
||||||
|
parameters["confidenceMinimum"] = strconv.Itoa(opts.ConfidenceMinimum)
|
||||||
|
}
|
||||||
|
if opts.Limit > 0 && opts.Limit <= 500_000 {
|
||||||
|
parameters["limit"] = strconv.Itoa(opts.Limit)
|
||||||
|
}
|
||||||
|
if opts.OnlyCountries != nil && len(opts.OnlyCountries) > 0 {
|
||||||
|
parameters["onlyCountries"] = strings.Join(opts.OnlyCountries, ",")
|
||||||
|
}
|
||||||
|
if opts.ExceptCountries != nil && len(opts.ExceptCountries) > 0 {
|
||||||
|
parameters["exceptCountries"] = strings.Join(opts.ExceptCountries, ",")
|
||||||
|
}
|
||||||
|
if opts.IpVersion == 4 || opts.IpVersion == 6 {
|
||||||
|
parameters["ipVersion"] = strconv.Itoa(opts.IpVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parameters
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Blacklist(ctx context.Context, opts *BlacklistOptions) (*BlacklistResult, error) {
|
||||||
|
var endpoint = "blacklist"
|
||||||
|
|
||||||
|
parameters := handleBlacklistOptions(opts)
|
||||||
|
|
||||||
|
req, err := c.NewRequest(http.MethodGet, endpoint, parameters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result *BlacklistResult
|
||||||
|
err = c.Do(ctx, req, result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) BlacklistPlain(ctx context.Context, opts *BlacklistOptions) (io.Reader, error) {
|
||||||
|
var endpoint = "blacklist"
|
||||||
|
|
||||||
|
parameters := handleBlacklistOptions(opts)
|
||||||
|
|
||||||
|
req, err := c.NewRequest(http.MethodGet, endpoint, parameters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", "text/plain")
|
||||||
|
|
||||||
|
resp, err := c.client.Do(req.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
var result *bytes.Buffer
|
||||||
|
_, err = io.Copy(result, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportOptions struct {
|
||||||
|
Categories []int `json:"categories"`
|
||||||
|
Comment string `json:"comment,omitempty"`
|
||||||
|
Time *time.Time `json:"timestamp,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportResult struct {
|
||||||
|
Data struct {
|
||||||
|
IpAddress string `json:"ipAddress"`
|
||||||
|
AbuseConfidenceScore int `json:"abuseConfidenceScore"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Report(ctx context.Context, ip net.IP, opts *ReportOptions) (*ReportsResult, error) {
|
||||||
|
var endpoint = "report"
|
||||||
|
|
||||||
|
ipAddress := html.EscapeString(ip.String())
|
||||||
|
parameters := map[string]string{
|
||||||
|
"ip": ipAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts != nil {
|
||||||
|
if opts.Categories != nil {
|
||||||
|
categories := make([]string, len(opts.Categories))
|
||||||
|
for _, category := range opts.Categories {
|
||||||
|
categories = append(categories, strconv.Itoa(category))
|
||||||
|
}
|
||||||
|
parameters["categories"] = strings.Join(categories, ",")
|
||||||
|
}
|
||||||
|
if opts.Comment != "" {
|
||||||
|
parameters["comment"] = opts.Comment
|
||||||
|
}
|
||||||
|
if opts.Time != nil {
|
||||||
|
parameters["timestamp"] = opts.Time.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := c.NewRequest(http.MethodGet, endpoint, parameters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var result *ReportsResult
|
||||||
|
err = c.Do(ctx, req, result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckBlockOptions struct {
|
||||||
|
MaxAgeInDays int `json:"maxAgeInDays,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckBlockResult struct {
|
||||||
|
Data struct {
|
||||||
|
NetworkAddress string `json:"networkAddress"`
|
||||||
|
Netmask string `json:"netmask"`
|
||||||
|
MinAddress string `json:"minAddress"`
|
||||||
|
MaxAddress string `json:"maxAddress"`
|
||||||
|
NumPossibleHosts int `json:"numPossibleHosts"`
|
||||||
|
AddressSpaceDesc string `json:"addressSpaceDesc"`
|
||||||
|
ReportedAddress []struct {
|
||||||
|
IpAddress string `json:"ipAddress"`
|
||||||
|
NumReports int `json:"numReports"`
|
||||||
|
MostRecentReport time.Time `json:"mostRecentReport"`
|
||||||
|
AbuseConfidenceScore int `json:"abuseConfidenceScore"`
|
||||||
|
CountryCode string `json:"countryCode"`
|
||||||
|
} `json:"reportedAddress,omitempty"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) CheckBlock(ctx context.Context, ipnNet net.IPNet, opts *CheckBlockOptions) (*CheckBlockResult, error) {
|
||||||
|
var endpoint = "check-block"
|
||||||
|
|
||||||
|
network := html.EscapeString(ipnNet.String())
|
||||||
|
parameters := map[string]string{
|
||||||
|
"network": network,
|
||||||
|
}
|
||||||
|
// TODO check network
|
||||||
|
if opts != nil {
|
||||||
|
if opts.MaxAgeInDays > 0 && opts.MaxAgeInDays <= 365 {
|
||||||
|
parameters["maxAgeInDays"] = strconv.Itoa(opts.MaxAgeInDays)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := c.NewRequest(http.MethodGet, endpoint, parameters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result *CheckBlockResult
|
||||||
|
err = c.Do(ctx, req, result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type BulkReportData struct {
|
||||||
|
IpAddress string `csv:"IP"`
|
||||||
|
Categories []string `csv:"Categories"`
|
||||||
|
ReportDate time.Time `csv:"ReportDate"`
|
||||||
|
Comment string `csv:"Comment"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BulkReportResult struct {
|
||||||
|
Data struct {
|
||||||
|
SavedReports int `json:"savedReports"`
|
||||||
|
InvalidReports []struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
Input string `json:"input"`
|
||||||
|
RowNumber int `json:"rowNumber"`
|
||||||
|
} `json:"invalidReports,omitempty"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) BulkReport(ctx context.Context, data *BulkReportData) (*BulkReportResult, error) {
|
||||||
|
//var endpoint = "bulk-report"
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClearAddressData struct {
|
||||||
|
Data struct {
|
||||||
|
NumReportsDeleted int `json:"numReportsDeleted"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ClearAddress(ctx context.Context, ip net.IP) (*ClearAddressData, error) {
|
||||||
|
var endpoint = "clear-address"
|
||||||
|
ipAddress := html.EscapeString(ip.String())
|
||||||
|
parameters := map[string]string{
|
||||||
|
"ipAddress": ipAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := c.NewRequest(http.MethodPost, endpoint, parameters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var result *ClearAddressData
|
||||||
|
err = c.Do(ctx, req, &result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
51
pkg/abuseipdb/rate/ratelimit.go
Normal file
51
pkg/abuseipdb/rate/ratelimit.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package rate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Category int
|
||||||
|
|
||||||
|
const (
|
||||||
|
CheckCategory Category = iota
|
||||||
|
ReportsCategory
|
||||||
|
BlacklistCategory
|
||||||
|
ReportCategory
|
||||||
|
CheckBlockCategory
|
||||||
|
BulkReportCategory
|
||||||
|
ClearAddressCategory
|
||||||
|
)
|
||||||
|
|
||||||
|
type Limit struct {
|
||||||
|
RetryAfter time.Time `header:"Retry-After"`
|
||||||
|
XRateLimitLimit int `header:"X-RateLimit-Limit"`
|
||||||
|
XRateLimitRemaining int `header:"X-RateLimit-Remaining"`
|
||||||
|
XRateLimitReset time.Time `header:"X-RateLimit-Reset"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Limits map[Category]*Limit
|
||||||
|
|
||||||
|
func (l *Limits) Update(category Category, header http.Header) error {
|
||||||
|
limit := &Limit{}
|
||||||
|
for key, value := range header {
|
||||||
|
iValue, err := strconv.Atoi(value[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch key {
|
||||||
|
case "X-RateLimit-Limit":
|
||||||
|
limit.XRateLimitLimit = iValue
|
||||||
|
case "X-RateLimit-Remaining":
|
||||||
|
limit.XRateLimitRemaining = iValue
|
||||||
|
case "X-RateLimit-Reset":
|
||||||
|
limit.XRateLimitReset = time.Unix(int64(iValue), 0)
|
||||||
|
case "Retry-After":
|
||||||
|
limit.RetryAfter = time.Now().Add(time.Duration(iValue) * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(*l)[category] = limit
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in a new issue