mirror of
https://github.com/nikdoof/aaisp-chaos.git
synced 2025-12-14 07:12:18 +00:00
Initial commit
This adds a rudimentary `chaos` package for querying broadband info, and a Prometheus exporter.
This commit is contained in:
166
chaos.go
Normal file
166
chaos.go
Normal file
@@ -0,0 +1,166 @@
|
||||
// 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"
|
||||
"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 (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("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
|
||||
}
|
||||
Reference in New Issue
Block a user