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 KeepAlive time.Duration } // 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: "", KeepAlive: 15 * time.Minute, }, 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 c.IAR.KeepAlive = time.Duration(resp.Data.GetTTDSetting.KeyValue) * time.Second } 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) { ticker := time.NewTicker(c.IAR.KeepAlive) defer ticker.Stop() slog.Info("Starting keep-alive sender", slog.Duration("interval", c.IAR.KeepAlive)) 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()) }