Files
go-iar-notificator/client/client.go
2025-09-08 13:27:09 -04:00

191 lines
5.2 KiB
Go

package client
import (
"context"
"encoding/json"
"errors"
"io"
"log/slog"
"sync"
"time"
"git.savin.nyc/alex/go-iar-notificator/config"
resty "resty.dev/v3"
)
const (
GET = "GET"
POST = "POST"
)
// Config holds the API configuration
// type Config struct {
// URL string
// SecretKey string
// AuthToken string
// SubscriberID int
// PagerGroupID string
// }
// Client represents a MySubaru API client that interacts with the MySubaru API.
type Client struct {
credentials *config.Credentials
IAR IaR
HTTPClient *resty.Client
isAlive bool
logger *slog.Logger
sync.RWMutex
}
type IaR struct {
PagerGroupID string
PagerGroupName string
Type string
}
// New function creates a New MySubaru API client
func New(credentials *config.Credentials, logger *slog.Logger) (*Client, error) {
client := &Client{
credentials: credentials,
IAR: IaR{
PagerGroupID: "",
PagerGroupName: "",
Type: "",
},
isAlive: false,
logger: logger,
}
httpClient := resty.New()
httpClient.
SetBaseURL("https://ttd.iamresponding.com/ttd").
SetHeaders(map[string]string{
"User-Agent": "python-requests/2.22.0",
"Accept-Encoding": "gzip, deflate",
"Accept": "*/*",
"Connection": "keep-alive",
"Content-Type": "application/json",
"SecretKey": client.credentials.SecretKey,
"Authorization": "TTDApiKey " + client.credentials.TTDApiKey,
},
)
client.HTTPClient = httpClient
return client, nil
}
func (c *Client) KeepAlive() error {
query := `query {getMutualPagerGroupsList {_id subscriberId name type tone_tolerance gaplength ignore_after record_delay record_seconds release_time playback_during_record post_email_command alert_command atone btone atonelength btonelength longtone longtonelength createDate},getTTDSetting(keySetting: "pollingSeconds"){keySetting keyValue}}`
resp, err := c.execute(POST, "", map[string]string{"query": query})
if err != nil {
return err
}
if resp != nil && len(resp.Data.GetMutualPagerGroupsList) > 0 {
pg := resp.Data.GetMutualPagerGroupsList[0]
c.IAR.PagerGroupID = pg.ID
c.IAR.PagerGroupName = pg.Name
c.IAR.Type = pg.Type
}
return nil
}
func (c *Client) PreAlert() (string, error) {
ttdReceivedDate := time.Now().UTC().Format(time.RFC3339)
query := `mutation {addAlert(ttdReceivedDate: "` + ttdReceivedDate + `", pagerGroup: ["` + c.IAR.PagerGroupID + `"]){_id textAlert pagerGroup subscriberId}}`
resp, err := c.execute(POST, "", map[string]string{"query": query})
if err != nil {
return "", err
}
return resp.Data.AddAlert.ID, nil
}
func (c *Client) Alert(audioBase64 string) error {
var err error
var alertID string
ttdReceivedDate := time.Now().UTC().Format(time.RFC3339)
if alertID, err = c.PreAlert(); err != nil {
return errors.New("failed to create pre-alert")
}
query := `mutation {addAlert(_id: "` + alertID + `", ttdReceivedDate: "` + ttdReceivedDate + `", pagerGroup: ["` + c.IAR.PagerGroupID + `"], audio: "` + audioBase64 + `"){_id textAlert pagerGroup audioUrl subscriberId}}`
resp, err := c.execute(POST, "", map[string]string{"query": query})
if err != nil {
return err
}
c.logger.Info("Alert sent successfully", "alertID", resp.Data.AddAlert.ID, "audioURL", resp.Data.AddAlert.AudioUrl)
return nil
}
// ServeKeepAlive starts a goroutine that sends keep-alive requests at the specified interval.
func (c *Client) ServeKeepAlive(ctx context.Context, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
slog.Info("Starting keep-alive sender", "interval", interval)
for {
select {
case <-ctx.Done():
slog.Info("Stopping keep-alive sender")
return
case <-ticker.C:
if err := c.KeepAlive(); err != nil {
slog.Error("Failed to send keep-alive", "error", err)
} else {
slog.Info("Sent keep-alive successfully")
}
}
}
}
// IsAlive checks if the Client instance is alive
func (c *Client) IsAlive() bool {
return c.isAlive
}
// execute executes an HTTP request based on the method, URL, and parameters provided.
func (c *Client) execute(method string, url string, params map[string]string) (*Response, error) {
c.Lock()
var resp *resty.Response
var err error
c.logger.Debug("executing http request", "method", method, "url", url, "params", params)
// POST Requests
if method == POST {
resp, err = c.HTTPClient.
R().
SetBody(params).
Post(url)
if err != nil {
c.logger.Error("error while executing POST request", "request", "execute", "method", method, "url", url, "error", err.Error())
return nil, err
}
c.logger.Debug("executed POST request", "method", method, "url", url, "params", params)
}
if resp.IsSuccess() {
resBytes, err := io.ReadAll(resp.Body)
if err != nil {
c.logger.Error("error while getting body", "error", err.Error())
}
c.logger.Debug("parsed http request output", "data", string(resBytes))
c.HTTPClient.SetCookies(resp.Cookies())
var r Response
if err := json.Unmarshal(resBytes, &r); err != nil {
c.logger.Error("error while unmarshalling response", "error", err.Error())
c.isAlive = false
c.Unlock()
return nil, err
}
c.isAlive = true
c.Unlock()
return &r, nil
}
c.isAlive = false
c.Unlock()
return nil, errors.New("request is not successfull, HTTP code: " + resp.Status())
}