mirror of
https://github.com/nikdoof/aaisp-chaos.git
synced 2025-12-13 06:42:16 +00:00
It's nice to third parties if you set User-Agent so the caller can be
identified. By default Go will set a User-Agent header to a string like
"Go-http-client/1.1".
The library now sets it to a string that identifies the source of the
program (github.com/jamesog/aaisp-chaos) and the compiled Go OS, arch
and version, to aid any potential debugging:
chaos-go (darwin; amd64; go1.15.5) github.com/jamesog/aaisp-chaos
177 lines
4.4 KiB
Go
177 lines
4.4 KiB
Go
// Package chaos provides access to Andrews and Arnold's CHAOS v2 API.
|
|
package chaos
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const defaultEndpoint = "https://chaos2.aa.net.uk"
|
|
|
|
// API provides the accessors for querying the CHAOS service.
|
|
type API struct {
|
|
Endpoint string
|
|
login url.Values
|
|
}
|
|
|
|
// New takes an Auth with API credentials and returns an API object.
|
|
func New(auth Auth) *API {
|
|
return &API{Endpoint: defaultEndpoint, login: auth.form()}
|
|
}
|
|
|
|
// Auth is the authentication credentials for the API.
|
|
//
|
|
// The API requires either account authentication (AccountNumber and AccountPassword) or control authentication (ControlLogin and ControlPassword.)
|
|
//
|
|
// ControlLogin may also be passed when using account authentication.
|
|
type Auth struct {
|
|
AccountNumber string
|
|
AccountPassword string
|
|
ControlLogin string
|
|
ControlPassword string
|
|
}
|
|
|
|
// Construct form values for sending as authentication data.
|
|
func (a Auth) form() url.Values {
|
|
f := url.Values{}
|
|
if a.AccountNumber != "" {
|
|
f.Set("account_number", a.AccountNumber)
|
|
}
|
|
if a.AccountPassword != "" {
|
|
f.Set("account_password", a.AccountPassword)
|
|
}
|
|
if a.ControlLogin != "" {
|
|
f.Set("control_login", a.ControlLogin)
|
|
}
|
|
if a.ControlPassword != "" {
|
|
f.Set("control_password", a.ControlPassword)
|
|
}
|
|
return f
|
|
}
|
|
|
|
func userAgent() string {
|
|
return fmt.Sprintf("chaos-go (%s; %s; %s) github.com/jamesog/aaisp-chaos",
|
|
runtime.GOOS,
|
|
runtime.GOARCH,
|
|
runtime.Version(),
|
|
)
|
|
}
|
|
|
|
func (api API) makeRequest(url string) ([]byte, error) {
|
|
client := &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", api.Endpoint+url, strings.NewReader(api.login.Encode()))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("User-Agent", userAgent())
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
defer resp.Body.Close()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error reading response body: %w", err)
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("bad response code: %d", resp.StatusCode)
|
|
}
|
|
|
|
return body, nil
|
|
}
|
|
|
|
// The API returns timestamps in the format "YYYY-mm-dd HH:mm:ss" rather than RFC3389.
|
|
//
|
|
// Use a custom type to unmarshal JSON to this format.
|
|
type chaosTime struct {
|
|
time.Time
|
|
}
|
|
|
|
func (t *chaosTime) UnmarshalJSON(b []byte) error {
|
|
s := strings.Trim(string(b), `"`)
|
|
// The API returns times in UK local rather than UTC
|
|
loc, err := time.LoadLocation("Europe/London")
|
|
if err != nil {
|
|
loc = time.Local
|
|
}
|
|
nt, err := time.ParseInLocation("2006-01-02 15:04:05", s, loc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
t.Time = nt
|
|
return nil
|
|
}
|
|
|
|
// BroadbandInfo represents information about a broadband line.
|
|
type BroadbandInfo struct {
|
|
ID int `json:"id,string"`
|
|
Login string `json:"login"`
|
|
Postcode string `json:"postcode"`
|
|
TXRate int `json:"tx_rate,string"`
|
|
RXRate int `json:"rx_rate,string"`
|
|
TXRateAdjusted int `json:"tx_rate_adjusted,string"`
|
|
QuotaMonthly int `json:"quota_monthly,string"`
|
|
QuotaRemaining int `json:"quota_remaining,string"`
|
|
QuotaTimestamp chaosTime `json:"quota_timestamp"`
|
|
}
|
|
|
|
// BroadbandInfo fetches broadband info.
|
|
func (api API) BroadbandInfo() ([]BroadbandInfo, error) {
|
|
resp, err := api.makeRequest("/broadband/info")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r := struct {
|
|
Info []BroadbandInfo `json:"info"`
|
|
Error string `json:"error"`
|
|
}{}
|
|
err = json.Unmarshal(resp, &r)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("BroadbandInfo JSON decode: %w", err)
|
|
}
|
|
if r.Error != "" {
|
|
return nil, errors.New(r.Error)
|
|
}
|
|
return r.Info, nil
|
|
}
|
|
|
|
// BroadbandQuota is quota.
|
|
type BroadbandQuota struct {
|
|
ID int `json:"id,string"`
|
|
QuotaMonthly int `json:"quota_monthly"`
|
|
QuotaRemaining int `json:"quota_remaining,string"`
|
|
QuotaTimestamp chaosTime `json:"quota_timestamp,string"`
|
|
}
|
|
|
|
// BroadbandQuota fetches the broadband quota.
|
|
func (api API) BroadbandQuota() ([]BroadbandQuota, error) {
|
|
resp, err := api.makeRequest("/broadband/quota")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r := struct {
|
|
Quota []BroadbandQuota `json:"quota"`
|
|
Error string `json:"error"`
|
|
}{}
|
|
err = json.Unmarshal(resp, &r)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("BroadbandQuota JSON decode: %w", err)
|
|
}
|
|
if r.Error != "" {
|
|
return nil, errors.New(r.Error)
|
|
}
|
|
return r.Quota, nil
|
|
}
|