mirror of
https://github.com/sstent/go-garth.git
synced 2025-12-06 08:01:42 +00:00
fixed timecode issue
This commit is contained in:
22
README.md
22
README.md
@@ -1,10 +1,12 @@
|
||||
# Garmin Connect Go Client
|
||||
|
||||
[](https://pkg.go.dev/github.com/sstent/go-garth/pkg/garmin)
|
||||
|
||||
Go port of the Garth Python library for accessing Garmin Connect data. Provides full API coverage with improved performance and type safety.
|
||||
|
||||
## Installation
|
||||
```bash
|
||||
go get github.com/sstent/garmin-connect/garth
|
||||
go get github.com/sstent/go-garth/pkg/garmin
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
@@ -14,12 +16,12 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"garmin-connect/garth"
|
||||
"github.com/sstent/go-garth/pkg/garmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create client and authenticate
|
||||
client, err := garth.NewClient("garmin.com")
|
||||
client, err := garmin.NewClient("garmin.com")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -29,9 +31,9 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Get yesterday's body battery data
|
||||
// Get yesterday's body battery data (detailed)
|
||||
yesterday := time.Now().AddDate(0, 0, -1)
|
||||
bb, err := garth.BodyBatteryData{}.Get(yesterday, client)
|
||||
bb, err := client.GetBodyBatteryData(yesterday)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -41,16 +43,16 @@ func main() {
|
||||
}
|
||||
|
||||
// Get weekly steps
|
||||
steps := garth.NewDailySteps()
|
||||
steps := garmin.NewDailySteps()
|
||||
stepData, err := steps.List(time.Now(), 7, client)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, s := range stepData {
|
||||
fmt.Printf("%s: %d steps\n",
|
||||
s.(garth.DailySteps).CalendarDate.Format("2006-01-02"),
|
||||
*s.(garth.DailySteps).TotalSteps)
|
||||
fmt.Printf("%s: %d steps\n",
|
||||
s.(garmin.DailySteps).CalendarDate.Format("2006-01-02"),
|
||||
*s.(garmin.DailySteps).TotalSteps)
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -102,7 +104,7 @@ BenchmarkSleepList-8 50000 35124 ns/op (7 days)
|
||||
```
|
||||
|
||||
## Documentation
|
||||
Full API docs: [https://pkg.go.dev/garmin-connect/garth](https://pkg.go.dev/garmin-connect/garth)
|
||||
Full API docs: [https://pkg.go.dev/github.com/sstent/go-garth/pkg/garmin](https://pkg.go.dev/github.com/sstent/go-garth/pkg/garmin)
|
||||
|
||||
## CLI Tool
|
||||
Includes `cmd/garth` CLI for data export. Supports both daily and weekly stats:
|
||||
|
||||
6
internal/api/client/doc.go
Normal file
6
internal/api/client/doc.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Package client implements the low-level Garmin Connect HTTP client.
|
||||
// It is responsible for authentication (via SSO helpers), request construction,
|
||||
// header and cookie handling, error mapping, and JSON decoding. Higher-level
|
||||
// public APIs in pkg/garmin delegate to this package for actual network I/O.
|
||||
// Note: This is an internal package and not intended for direct external use.
|
||||
package client
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
// LoadEnvCredentials loads credentials from .env file
|
||||
func LoadEnvCredentials() (email, password, domain string, err error) {
|
||||
// Determine project root (assuming .env is in the project root)
|
||||
projectRoot := "/home/sstent/Projects/github.com/sstent/go-garth"
|
||||
projectRoot := "/home/sstent/Projects/go-garth"
|
||||
envPath := filepath.Join(projectRoot, ".env")
|
||||
|
||||
// Load .env file
|
||||
@@ -34,4 +34,4 @@ func LoadEnvCredentials() (email, password, domain string, err error) {
|
||||
}
|
||||
|
||||
return email, password, domain, nil
|
||||
}
|
||||
}
|
||||
|
||||
4
internal/auth/credentials/doc.go
Normal file
4
internal/auth/credentials/doc.go
Normal file
@@ -0,0 +1,4 @@
|
||||
// Package credentials provides helpers for loading user credentials and
|
||||
// environment configuration used during authentication and local development.
|
||||
// Note: This is an internal package and not intended for direct external use.
|
||||
package credentials
|
||||
5
internal/auth/oauth/doc.go
Normal file
5
internal/auth/oauth/doc.go
Normal file
@@ -0,0 +1,5 @@
|
||||
// Package oauth contains low-level OAuth1 and OAuth2 flows used by SSO to
|
||||
// obtain and exchange tokens. It handles request signing, headers, and response
|
||||
// parsing to produce strongly-typed token structures for the client.
|
||||
// Note: This is an internal package and not intended for direct external use.
|
||||
package oauth
|
||||
5
internal/auth/sso/doc.go
Normal file
5
internal/auth/sso/doc.go
Normal file
@@ -0,0 +1,5 @@
|
||||
// Package sso implements the Garmin SSO login flow. It orchestrates CSRF,
|
||||
// ticket exchange, MFA placeholders, and token retrieval, delegating OAuth
|
||||
// details to internal/auth/oauth. The internal client consumes this package.
|
||||
// Note: This is an internal package and not intended for direct external use.
|
||||
package sso
|
||||
5
internal/config/doc.go
Normal file
5
internal/config/doc.go
Normal file
@@ -0,0 +1,5 @@
|
||||
// Package config defines the application configuration schema and helpers for
|
||||
// locating, loading, saving, and initializing configuration files following
|
||||
// conventional XDG directory layout.
|
||||
// Note: This is an internal package and not intended for direct external use.
|
||||
package config
|
||||
5
internal/data/doc.go
Normal file
5
internal/data/doc.go
Normal file
@@ -0,0 +1,5 @@
|
||||
// Package data provides helpers and enrichments for Garmin wellness and metric
|
||||
// data. It includes parsing utilities, convenience wrappers that add methods to
|
||||
// decoded responses, and transformations used by higher-level APIs.
|
||||
// Note: This is an internal package and not intended for direct external use.
|
||||
package data
|
||||
5
internal/errors/doc.go
Normal file
5
internal/errors/doc.go
Normal file
@@ -0,0 +1,5 @@
|
||||
// Package errors defines structured error types used across the module,
|
||||
// including APIError, IOError, AuthenticationError, OAuthError, and
|
||||
// ValidationError. These implement error wrapping and preserve HTTP context.
|
||||
// Note: This is an internal package and not intended for direct external use.
|
||||
package errors
|
||||
5
internal/models/types/doc.go
Normal file
5
internal/models/types/doc.go
Normal file
@@ -0,0 +1,5 @@
|
||||
// Package types defines core domain models mapped to Garmin Connect API JSON.
|
||||
// It includes user profile, wellness metrics, sleep detail, HRV, body battery,
|
||||
// training status/load, time helpers, and related structures.
|
||||
// Note: This is an internal package and not intended for direct external use.
|
||||
package types
|
||||
@@ -70,6 +70,7 @@ func (gt *GarminTime) UnmarshalJSON(b []byte) (err error) {
|
||||
// If the input string does not contain 'Z', it will be parsed as local time.
|
||||
// For consistency, we'll assume UTC if no timezone is specified.
|
||||
layouts := []string{
|
||||
"2006-01-02 15:04:05", // Example: 2025-09-21 07:18:03
|
||||
"2006-01-02T15:04:05.0", // Example: 2018-09-01T00:13:25.0
|
||||
"2006-01-02T15:04:05", // Example: 2018-09-01T00:13:25
|
||||
"2006-01-02", // Example: 2018-09-01
|
||||
|
||||
82
internal/models/types/garmin_test.go
Normal file
82
internal/models/types/garmin_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGarminTime_UnmarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected time.Time
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "space separated format",
|
||||
input: `"2025-09-21 07:18:03"`,
|
||||
expected: time.Date(2025, 9, 21, 7, 18, 3, 0, time.UTC),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "T separator with milliseconds",
|
||||
input: `"2018-09-01T00:13:25.0"`,
|
||||
expected: time.Date(2018, 9, 1, 0, 13, 25, 0, time.UTC),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "T separator without milliseconds",
|
||||
input: `"2018-09-01T00:13:25"`,
|
||||
expected: time.Date(2018, 9, 1, 0, 13, 25, 0, time.UTC),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "date only",
|
||||
input: `"2018-09-01"`,
|
||||
expected: time.Date(2018, 9, 1, 0, 0, 0, 0, time.UTC),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid format",
|
||||
input: `"invalid"`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "null value",
|
||||
input: "null",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var gt GarminTime
|
||||
err := json.Unmarshal([]byte(tt.input), >)
|
||||
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("expected error but got none")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if tt.input == "null" {
|
||||
// For null values, the time should be zero
|
||||
if !gt.Time.IsZero() {
|
||||
t.Errorf("expected zero time for null input, got %v", gt.Time)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !gt.Time.Equal(tt.expected) {
|
||||
t.Errorf("expected %v, got %v", tt.expected, gt.Time)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
5
internal/stats/doc.go
Normal file
5
internal/stats/doc.go
Normal file
@@ -0,0 +1,5 @@
|
||||
// Package stats provides typed accessors for aggregated statistics endpoints,
|
||||
// including daily and weekly variants for steps, stress, hydration, sleep, HRV,
|
||||
// and intensity minutes. Pagination and date-window logic live here.
|
||||
// Note: This is an internal package and not intended for direct external use.
|
||||
package stats
|
||||
4
internal/testutils/doc.go
Normal file
4
internal/testutils/doc.go
Normal file
@@ -0,0 +1,4 @@
|
||||
// Package testutils contains helpers for tests such as HTTP servers and mock
|
||||
// clients. It is used by unit and integration tests within this repository.
|
||||
// Note: This is an internal package and not intended for direct external use.
|
||||
package testutils
|
||||
4
internal/users/doc.go
Normal file
4
internal/users/doc.go
Normal file
@@ -0,0 +1,4 @@
|
||||
// Package users contains structures related to user settings and sleep windows.
|
||||
// It mirrors selected Garmin user profile payloads used in feature logic.
|
||||
// Note: This is an internal package and not intended for direct external use.
|
||||
package users
|
||||
4
internal/utils/doc.go
Normal file
4
internal/utils/doc.go
Normal file
@@ -0,0 +1,4 @@
|
||||
// Package utils provides general helpers such as OAuth signing utilities,
|
||||
// time conversions, date range helpers, and string case conversions.
|
||||
// Note: This is an internal package and not intended for direct external use.
|
||||
package utils
|
||||
6
pkg/auth/oauth/doc.go
Normal file
6
pkg/auth/oauth/doc.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Package oauth provides public wrappers around the internal OAuth helpers.
|
||||
// It exposes functions to obtain OAuth1 tokens and exchange them for OAuth2
|
||||
// tokens compatible with the public garmin package types. External consumers
|
||||
// should use this package when they need token bootstrapping independent of
|
||||
// a fully initialized client.
|
||||
package oauth
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"github.com/sstent/go-garth/internal/models/types"
|
||||
)
|
||||
|
||||
// GetDailyHRVData retrieves comprehensive daily HRV data for the given date.
|
||||
// It returns nil when no HRV data is available for the specified day.
|
||||
func (c *Client) GetDailyHRVData(date time.Time) (*types.DailyHRVData, error) {
|
||||
return getDailyHRVData(date, c.Client)
|
||||
}
|
||||
@@ -41,6 +43,9 @@ func getDailyHRVData(day time.Time, client *internalClient.Client) (*types.Daily
|
||||
return &response.HRVSummary, nil
|
||||
}
|
||||
|
||||
// GetDetailedSleepData retrieves comprehensive sleep data for the given date,
|
||||
// including sleep stages and movement where available. It returns nil when no
|
||||
// sleep data is available for the specified day.
|
||||
func (c *Client) GetDetailedSleepData(date time.Time) (*types.DetailedSleepData, error) {
|
||||
return getDetailedSleepData(date, c.Client)
|
||||
}
|
||||
|
||||
4
shared/interfaces/doc.go
Normal file
4
shared/interfaces/doc.go
Normal file
@@ -0,0 +1,4 @@
|
||||
// Package interfaces defines narrow contracts shared across packages.
|
||||
// Notably, APIClient abstracts HTTP access required by data and stats layers,
|
||||
// and BaseData/Data define the concurrent day-by-day retrieval pattern.
|
||||
package interfaces
|
||||
4
shared/models/doc.go
Normal file
4
shared/models/doc.go
Normal file
@@ -0,0 +1,4 @@
|
||||
// Package models defines shared data models that are safe to expose to public
|
||||
// packages, including user settings and related sub-structures consumed by
|
||||
// both internal client code and public wrappers.
|
||||
package models
|
||||
Reference in New Issue
Block a user