mirror of
https://github.com/sstent/go-garth.git
synced 2026-01-25 08:35:06 +00:00
porting - part9 done
This commit is contained in:
@@ -66,20 +66,22 @@ func (c *Client) Login(email, password string) error {
|
|||||||
c.AuthToken = fmt.Sprintf("%s %s", oauth2Token.TokenType, oauth2Token.AccessToken)
|
c.AuthToken = fmt.Sprintf("%s %s", oauth2Token.TokenType, oauth2Token.AccessToken)
|
||||||
|
|
||||||
// Get user profile to set username
|
// Get user profile to set username
|
||||||
if err := c.GetUserProfile(); err != nil {
|
profile, err := c.GetUserProfile()
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get user profile after login: %w", err)
|
return fmt.Errorf("failed to get user profile after login: %w", err)
|
||||||
}
|
}
|
||||||
|
c.Username = profile.UserName
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserProfile retrieves the current user's profile
|
// GetUserProfile retrieves the current user's full profile
|
||||||
func (c *Client) GetUserProfile() error {
|
func (c *Client) GetUserProfile() (*UserProfile, error) {
|
||||||
profileURL := fmt.Sprintf("https://connectapi.%s/userprofile-service/socialProfile", c.Domain)
|
profileURL := fmt.Sprintf("https://connectapi.%s/userprofile-service/socialProfile", c.Domain)
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", profileURL, nil)
|
req, err := http.NewRequest("GET", profileURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create profile request: %w", err)
|
return nil, fmt.Errorf("failed to create profile request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Authorization", c.AuthToken)
|
req.Header.Set("Authorization", c.AuthToken)
|
||||||
@@ -87,26 +89,21 @@ func (c *Client) GetUserProfile() error {
|
|||||||
|
|
||||||
resp, err := c.HTTPClient.Do(req)
|
resp, err := c.HTTPClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get user profile: %w", err)
|
return nil, fmt.Errorf("failed to get user profile: %w", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
body, _ := io.ReadAll(resp.Body)
|
body, _ := io.ReadAll(resp.Body)
|
||||||
return fmt.Errorf("profile request failed with status %d: %s", resp.StatusCode, string(body))
|
return nil, fmt.Errorf("profile request failed with status %d: %s", resp.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
var profile map[string]interface{}
|
var profile UserProfile
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&profile); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&profile); err != nil {
|
||||||
return fmt.Errorf("failed to parse profile: %w", err)
|
return nil, fmt.Errorf("failed to parse profile: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if username, ok := profile["userName"].(string); ok {
|
return &profile, nil
|
||||||
c.Username = username
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("username not found in profile response")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetActivities retrieves recent activities
|
// GetActivities retrieves recent activities
|
||||||
|
|||||||
71
garth/client/profile.go
Normal file
71
garth/client/profile.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserProfile struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
ProfileID int `json:"profileId"`
|
||||||
|
GarminGUID string `json:"garminGuid"`
|
||||||
|
DisplayName string `json:"displayName"`
|
||||||
|
FullName string `json:"fullName"`
|
||||||
|
UserName string `json:"userName"`
|
||||||
|
ProfileImageType *string `json:"profileImageType"`
|
||||||
|
ProfileImageURLLarge *string `json:"profileImageUrlLarge"`
|
||||||
|
ProfileImageURLMedium *string `json:"profileImageUrlMedium"`
|
||||||
|
ProfileImageURLSmall *string `json:"profileImageUrlSmall"`
|
||||||
|
Location *string `json:"location"`
|
||||||
|
FacebookURL *string `json:"facebookUrl"`
|
||||||
|
TwitterURL *string `json:"twitterUrl"`
|
||||||
|
PersonalWebsite *string `json:"personalWebsite"`
|
||||||
|
Motivation *string `json:"motivation"`
|
||||||
|
Bio *string `json:"bio"`
|
||||||
|
PrimaryActivity *string `json:"primaryActivity"`
|
||||||
|
FavoriteActivityTypes []string `json:"favoriteActivityTypes"`
|
||||||
|
RunningTrainingSpeed float64 `json:"runningTrainingSpeed"`
|
||||||
|
CyclingTrainingSpeed float64 `json:"cyclingTrainingSpeed"`
|
||||||
|
FavoriteCyclingActivityTypes []string `json:"favoriteCyclingActivityTypes"`
|
||||||
|
CyclingClassification *string `json:"cyclingClassification"`
|
||||||
|
CyclingMaxAvgPower float64 `json:"cyclingMaxAvgPower"`
|
||||||
|
SwimmingTrainingSpeed float64 `json:"swimmingTrainingSpeed"`
|
||||||
|
ProfileVisibility string `json:"profileVisibility"`
|
||||||
|
ActivityStartVisibility string `json:"activityStartVisibility"`
|
||||||
|
ActivityMapVisibility string `json:"activityMapVisibility"`
|
||||||
|
CourseVisibility string `json:"courseVisibility"`
|
||||||
|
ActivityHeartRateVisibility string `json:"activityHeartRateVisibility"`
|
||||||
|
ActivityPowerVisibility string `json:"activityPowerVisibility"`
|
||||||
|
BadgeVisibility string `json:"badgeVisibility"`
|
||||||
|
ShowAge bool `json:"showAge"`
|
||||||
|
ShowWeight bool `json:"showWeight"`
|
||||||
|
ShowHeight bool `json:"showHeight"`
|
||||||
|
ShowWeightClass bool `json:"showWeightClass"`
|
||||||
|
ShowAgeRange bool `json:"showAgeRange"`
|
||||||
|
ShowGender bool `json:"showGender"`
|
||||||
|
ShowActivityClass bool `json:"showActivityClass"`
|
||||||
|
ShowVO2Max bool `json:"showVo2Max"`
|
||||||
|
ShowPersonalRecords bool `json:"showPersonalRecords"`
|
||||||
|
ShowLast12Months bool `json:"showLast12Months"`
|
||||||
|
ShowLifetimeTotals bool `json:"showLifetimeTotals"`
|
||||||
|
ShowUpcomingEvents bool `json:"showUpcomingEvents"`
|
||||||
|
ShowRecentFavorites bool `json:"showRecentFavorites"`
|
||||||
|
ShowRecentDevice bool `json:"showRecentDevice"`
|
||||||
|
ShowRecentGear bool `json:"showRecentGear"`
|
||||||
|
ShowBadges bool `json:"showBadges"`
|
||||||
|
OtherActivity *string `json:"otherActivity"`
|
||||||
|
OtherPrimaryActivity *string `json:"otherPrimaryActivity"`
|
||||||
|
OtherMotivation *string `json:"otherMotivation"`
|
||||||
|
UserRoles []string `json:"userRoles"`
|
||||||
|
NameApproved bool `json:"nameApproved"`
|
||||||
|
UserProfileFullName string `json:"userProfileFullName"`
|
||||||
|
MakeGolfScorecardsPrivate bool `json:"makeGolfScorecardsPrivate"`
|
||||||
|
AllowGolfLiveScoring bool `json:"allowGolfLiveScoring"`
|
||||||
|
AllowGolfScoringByConnections bool `json:"allowGolfScoringByConnections"`
|
||||||
|
UserLevel int `json:"userLevel"`
|
||||||
|
UserPoint int `json:"userPoint"`
|
||||||
|
LevelUpdateDate time.Time `json:"levelUpdateDate"`
|
||||||
|
LevelIsViewed bool `json:"levelIsViewed"`
|
||||||
|
LevelPointThreshold int `json:"levelPointThreshold"`
|
||||||
|
UserPointOffset int `json:"userPointOffset"`
|
||||||
|
UserPro bool `json:"userPro"`
|
||||||
|
}
|
||||||
122
garth/client/settings.go
Normal file
122
garth/client/settings.go
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PowerFormat struct {
|
||||||
|
FormatID int `json:"formatId"`
|
||||||
|
FormatKey string `json:"formatKey"`
|
||||||
|
MinFraction int `json:"minFraction"`
|
||||||
|
MaxFraction int `json:"maxFraction"`
|
||||||
|
GroupingUsed bool `json:"groupingUsed"`
|
||||||
|
DisplayFormat *string `json:"displayFormat"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirstDayOfWeek struct {
|
||||||
|
DayID int `json:"dayId"`
|
||||||
|
DayName string `json:"dayName"`
|
||||||
|
SortOrder int `json:"sortOrder"`
|
||||||
|
IsPossibleFirstDay bool `json:"isPossibleFirstDay"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WeatherLocation struct {
|
||||||
|
UseFixedLocation *bool `json:"useFixedLocation"`
|
||||||
|
Latitude *float64 `json:"latitude"`
|
||||||
|
Longitude *float64 `json:"longitude"`
|
||||||
|
LocationName *string `json:"locationName"`
|
||||||
|
ISOCountryCode *string `json:"isoCountryCode"`
|
||||||
|
PostalCode *string `json:"postalCode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserData struct {
|
||||||
|
Gender string `json:"gender"`
|
||||||
|
Weight float64 `json:"weight"`
|
||||||
|
Height float64 `json:"height"`
|
||||||
|
TimeFormat string `json:"timeFormat"`
|
||||||
|
BirthDate time.Time `json:"birthDate"`
|
||||||
|
MeasurementSystem string `json:"measurementSystem"`
|
||||||
|
ActivityLevel *string `json:"activityLevel"`
|
||||||
|
Handedness string `json:"handedness"`
|
||||||
|
PowerFormat PowerFormat `json:"powerFormat"`
|
||||||
|
HeartRateFormat PowerFormat `json:"heartRateFormat"`
|
||||||
|
FirstDayOfWeek FirstDayOfWeek `json:"firstDayOfWeek"`
|
||||||
|
VO2MaxRunning *float64 `json:"vo2MaxRunning"`
|
||||||
|
VO2MaxCycling *float64 `json:"vo2MaxCycling"`
|
||||||
|
LactateThresholdSpeed *float64 `json:"lactateThresholdSpeed"`
|
||||||
|
LactateThresholdHeartRate *float64 `json:"lactateThresholdHeartRate"`
|
||||||
|
DiveNumber *int `json:"diveNumber"`
|
||||||
|
IntensityMinutesCalcMethod string `json:"intensityMinutesCalcMethod"`
|
||||||
|
ModerateIntensityMinutesHRZone int `json:"moderateIntensityMinutesHrZone"`
|
||||||
|
VigorousIntensityMinutesHRZone int `json:"vigorousIntensityMinutesHrZone"`
|
||||||
|
HydrationMeasurementUnit string `json:"hydrationMeasurementUnit"`
|
||||||
|
HydrationContainers []map[string]interface{} `json:"hydrationContainers"`
|
||||||
|
HydrationAutoGoalEnabled bool `json:"hydrationAutoGoalEnabled"`
|
||||||
|
FirstbeatMaxStressScore *float64 `json:"firstbeatMaxStressScore"`
|
||||||
|
FirstbeatCyclingLTTimestamp *int64 `json:"firstbeatCyclingLtTimestamp"`
|
||||||
|
FirstbeatRunningLTTimestamp *int64 `json:"firstbeatRunningLtTimestamp"`
|
||||||
|
ThresholdHeartRateAutoDetected bool `json:"thresholdHeartRateAutoDetected"`
|
||||||
|
FTPAutoDetected *bool `json:"ftpAutoDetected"`
|
||||||
|
TrainingStatusPausedDate *string `json:"trainingStatusPausedDate"`
|
||||||
|
WeatherLocation *WeatherLocation `json:"weatherLocation"`
|
||||||
|
GolfDistanceUnit *string `json:"golfDistanceUnit"`
|
||||||
|
GolfElevationUnit *string `json:"golfElevationUnit"`
|
||||||
|
GolfSpeedUnit *string `json:"golfSpeedUnit"`
|
||||||
|
ExternalBottomTime *float64 `json:"externalBottomTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserSleep struct {
|
||||||
|
SleepTime int `json:"sleepTime"`
|
||||||
|
DefaultSleepTime bool `json:"defaultSleepTime"`
|
||||||
|
WakeTime int `json:"wakeTime"`
|
||||||
|
DefaultWakeTime bool `json:"defaultWakeTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserSleepWindow struct {
|
||||||
|
SleepWindowFrequency string `json:"sleepWindowFrequency"`
|
||||||
|
StartSleepTimeSecondsFromMidnight int `json:"startSleepTimeSecondsFromMidnight"`
|
||||||
|
EndSleepTimeSecondsFromMidnight int `json:"endSleepTimeSecondsFromMidnight"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserSettings struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
UserData UserData `json:"userData"`
|
||||||
|
UserSleep UserSleep `json:"userSleep"`
|
||||||
|
ConnectDate *string `json:"connectDate"`
|
||||||
|
SourceType *string `json:"sourceType"`
|
||||||
|
UserSleepWindows []UserSleepWindow `json:"userSleepWindows,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetUserSettings() (*UserSettings, error) {
|
||||||
|
settingsURL := fmt.Sprintf("https://connectapi.%s/userprofile-service/userprofile/user-settings", c.Domain)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", settingsURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create settings request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", c.AuthToken)
|
||||||
|
req.Header.Set("User-Agent", "com.garmin.android.apps.connectmobile")
|
||||||
|
|
||||||
|
resp, err := c.HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get user settings: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return nil, fmt.Errorf("settings request failed with status %d: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings UserSettings
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&settings); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse settings: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &settings, nil
|
||||||
|
}
|
||||||
71
garth/users/profile.go
Normal file
71
garth/users/profile.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserProfile struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
ProfileID int `json:"profileId"`
|
||||||
|
GarminGUID string `json:"garminGuid"`
|
||||||
|
DisplayName string `json:"displayName"`
|
||||||
|
FullName string `json:"fullName"`
|
||||||
|
UserName string `json:"userName"`
|
||||||
|
ProfileImageType *string `json:"profileImageType"`
|
||||||
|
ProfileImageURLLarge *string `json:"profileImageUrlLarge"`
|
||||||
|
ProfileImageURLMedium *string `json:"profileImageUrlMedium"`
|
||||||
|
ProfileImageURLSmall *string `json:"profileImageUrlSmall"`
|
||||||
|
Location *string `json:"location"`
|
||||||
|
FacebookURL *string `json:"facebookUrl"`
|
||||||
|
TwitterURL *string `json:"twitterUrl"`
|
||||||
|
PersonalWebsite *string `json:"personalWebsite"`
|
||||||
|
Motivation *string `json:"motivation"`
|
||||||
|
Bio *string `json:"bio"`
|
||||||
|
PrimaryActivity *string `json:"primaryActivity"`
|
||||||
|
FavoriteActivityTypes []string `json:"favoriteActivityTypes"`
|
||||||
|
RunningTrainingSpeed float64 `json:"runningTrainingSpeed"`
|
||||||
|
CyclingTrainingSpeed float64 `json:"cyclingTrainingSpeed"`
|
||||||
|
FavoriteCyclingActivityTypes []string `json:"favoriteCyclingActivityTypes"`
|
||||||
|
CyclingClassification *string `json:"cyclingClassification"`
|
||||||
|
CyclingMaxAvgPower float64 `json:"cyclingMaxAvgPower"`
|
||||||
|
SwimmingTrainingSpeed float64 `json:"swimmingTrainingSpeed"`
|
||||||
|
ProfileVisibility string `json:"profileVisibility"`
|
||||||
|
ActivityStartVisibility string `json:"activityStartVisibility"`
|
||||||
|
ActivityMapVisibility string `json:"activityMapVisibility"`
|
||||||
|
CourseVisibility string `json:"courseVisibility"`
|
||||||
|
ActivityHeartRateVisibility string `json:"activityHeartRateVisibility"`
|
||||||
|
ActivityPowerVisibility string `json:"activityPowerVisibility"`
|
||||||
|
BadgeVisibility string `json:"badgeVisibility"`
|
||||||
|
ShowAge bool `json:"showAge"`
|
||||||
|
ShowWeight bool `json:"showWeight"`
|
||||||
|
ShowHeight bool `json:"showHeight"`
|
||||||
|
ShowWeightClass bool `json:"showWeightClass"`
|
||||||
|
ShowAgeRange bool `json:"showAgeRange"`
|
||||||
|
ShowGender bool `json:"showGender"`
|
||||||
|
ShowActivityClass bool `json:"showActivityClass"`
|
||||||
|
ShowVO2Max bool `json:"showVo2Max"`
|
||||||
|
ShowPersonalRecords bool `json:"showPersonalRecords"`
|
||||||
|
ShowLast12Months bool `json:"showLast12Months"`
|
||||||
|
ShowLifetimeTotals bool `json:"showLifetimeTotals"`
|
||||||
|
ShowUpcomingEvents bool `json:"showUpcomingEvents"`
|
||||||
|
ShowRecentFavorites bool `json:"showRecentFavorites"`
|
||||||
|
ShowRecentDevice bool `json:"showRecentDevice"`
|
||||||
|
ShowRecentGear bool `json:"showRecentGear"`
|
||||||
|
ShowBadges bool `json:"showBadges"`
|
||||||
|
OtherActivity *string `json:"otherActivity"`
|
||||||
|
OtherPrimaryActivity *string `json:"otherPrimaryActivity"`
|
||||||
|
OtherMotivation *string `json:"otherMotivation"`
|
||||||
|
UserRoles []string `json:"userRoles"`
|
||||||
|
NameApproved bool `json:"nameApproved"`
|
||||||
|
UserProfileFullName string `json:"userProfileFullName"`
|
||||||
|
MakeGolfScorecardsPrivate bool `json:"makeGolfScorecardsPrivate"`
|
||||||
|
AllowGolfLiveScoring bool `json:"allowGolfLiveScoring"`
|
||||||
|
AllowGolfScoringByConnections bool `json:"allowGolfScoringByConnections"`
|
||||||
|
UserLevel int `json:"userLevel"`
|
||||||
|
UserPoint int `json:"userPoint"`
|
||||||
|
LevelUpdateDate time.Time `json:"levelUpdateDate"`
|
||||||
|
LevelIsViewed bool `json:"levelIsViewed"`
|
||||||
|
LevelPointThreshold int `json:"levelPointThreshold"`
|
||||||
|
UserPointOffset int `json:"userPointOffset"`
|
||||||
|
UserPro bool `json:"userPro"`
|
||||||
|
}
|
||||||
95
garth/users/settings.go
Normal file
95
garth/users/settings.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"garmin-connect/garth/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PowerFormat struct {
|
||||||
|
FormatID int `json:"formatId"`
|
||||||
|
FormatKey string `json:"formatKey"`
|
||||||
|
MinFraction int `json:"minFraction"`
|
||||||
|
MaxFraction int `json:"maxFraction"`
|
||||||
|
GroupingUsed bool `json:"groupingUsed"`
|
||||||
|
DisplayFormat *string `json:"displayFormat"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirstDayOfWeek struct {
|
||||||
|
DayID int `json:"dayId"`
|
||||||
|
DayName string `json:"dayName"`
|
||||||
|
SortOrder int `json:"sortOrder"`
|
||||||
|
IsPossibleFirstDay bool `json:"isPossibleFirstDay"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WeatherLocation struct {
|
||||||
|
UseFixedLocation *bool `json:"useFixedLocation"`
|
||||||
|
Latitude *float64 `json:"latitude"`
|
||||||
|
Longitude *float64 `json:"longitude"`
|
||||||
|
LocationName *string `json:"locationName"`
|
||||||
|
ISOCountryCode *string `json:"isoCountryCode"`
|
||||||
|
PostalCode *string `json:"postalCode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserData struct {
|
||||||
|
Gender string `json:"gender"`
|
||||||
|
Weight float64 `json:"weight"`
|
||||||
|
Height float64 `json:"height"`
|
||||||
|
TimeFormat string `json:"timeFormat"`
|
||||||
|
BirthDate time.Time `json:"birthDate"`
|
||||||
|
MeasurementSystem string `json:"measurementSystem"`
|
||||||
|
ActivityLevel *string `json:"activityLevel"`
|
||||||
|
Handedness string `json:"handedness"`
|
||||||
|
PowerFormat PowerFormat `json:"powerFormat"`
|
||||||
|
HeartRateFormat PowerFormat `json:"heartRateFormat"`
|
||||||
|
FirstDayOfWeek FirstDayOfWeek `json:"firstDayOfWeek"`
|
||||||
|
VO2MaxRunning *float64 `json:"vo2MaxRunning"`
|
||||||
|
VO2MaxCycling *float64 `json:"vo2MaxCycling"`
|
||||||
|
LactateThresholdSpeed *float64 `json:"lactateThresholdSpeed"`
|
||||||
|
LactateThresholdHeartRate *float64 `json:"lactateThresholdHeartRate"`
|
||||||
|
DiveNumber *int `json:"diveNumber"`
|
||||||
|
IntensityMinutesCalcMethod string `json:"intensityMinutesCalcMethod"`
|
||||||
|
ModerateIntensityMinutesHRZone int `json:"moderateIntensityMinutesHrZone"`
|
||||||
|
VigorousIntensityMinutesHRZone int `json:"vigorousIntensityMinutesHrZone"`
|
||||||
|
HydrationMeasurementUnit string `json:"hydrationMeasurementUnit"`
|
||||||
|
HydrationContainers []map[string]interface{} `json:"hydrationContainers"`
|
||||||
|
HydrationAutoGoalEnabled bool `json:"hydrationAutoGoalEnabled"`
|
||||||
|
FirstbeatMaxStressScore *float64 `json:"firstbeatMaxStressScore"`
|
||||||
|
FirstbeatCyclingLTTimestamp *int64 `json:"firstbeatCyclingLtTimestamp"`
|
||||||
|
FirstbeatRunningLTTimestamp *int64 `json:"firstbeatRunningLtTimestamp"`
|
||||||
|
ThresholdHeartRateAutoDetected bool `json:"thresholdHeartRateAutoDetected"`
|
||||||
|
FTPAutoDetected *bool `json:"ftpAutoDetected"`
|
||||||
|
TrainingStatusPausedDate *string `json:"trainingStatusPausedDate"`
|
||||||
|
WeatherLocation *WeatherLocation `json:"weatherLocation"`
|
||||||
|
GolfDistanceUnit *string `json:"golfDistanceUnit"`
|
||||||
|
GolfElevationUnit *string `json:"golfElevationUnit"`
|
||||||
|
GolfSpeedUnit *string `json:"golfSpeedUnit"`
|
||||||
|
ExternalBottomTime *float64 `json:"externalBottomTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserSleep struct {
|
||||||
|
SleepTime int `json:"sleepTime"`
|
||||||
|
DefaultSleepTime bool `json:"defaultSleepTime"`
|
||||||
|
WakeTime int `json:"wakeTime"`
|
||||||
|
DefaultWakeTime bool `json:"defaultWakeTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserSleepWindow struct {
|
||||||
|
SleepWindowFrequency string `json:"sleepWindowFrequency"`
|
||||||
|
StartSleepTimeSecondsFromMidnight int `json:"startSleepTimeSecondsFromMidnight"`
|
||||||
|
EndSleepTimeSecondsFromMidnight int `json:"endSleepTimeSecondsFromMidnight"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserSettings struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
UserData UserData `json:"userData"`
|
||||||
|
UserSleep UserSleep `json:"userSleep"`
|
||||||
|
ConnectDate *string `json:"connectDate"`
|
||||||
|
SourceType *string `json:"sourceType"`
|
||||||
|
UserSleepWindows []UserSleepWindow `json:"userSleepWindows,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSettings(c *client.Client) (*UserSettings, error) {
|
||||||
|
// Implementation will be added in client.go
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -152,3 +153,65 @@ func DateRange(end time.Time, days int) []time.Time {
|
|||||||
}
|
}
|
||||||
return dates
|
return dates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CamelToSnake converts a camelCase string to snake_case
|
||||||
|
func CamelToSnake(s string) string {
|
||||||
|
matchFirstCap := regexp.MustCompile("(.)([A-Z][a-z]+)")
|
||||||
|
matchAllCap := regexp.MustCompile("([a-z0-9])([A-Z])")
|
||||||
|
|
||||||
|
snake := matchFirstCap.ReplaceAllString(s, "${1}_${2}")
|
||||||
|
snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}")
|
||||||
|
return strings.ToLower(snake)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CamelToSnakeDict recursively converts map keys from camelCase to snake_case
|
||||||
|
func CamelToSnakeDict(m map[string]interface{}) map[string]interface{} {
|
||||||
|
snakeDict := make(map[string]interface{})
|
||||||
|
for k, v := range m {
|
||||||
|
snakeKey := CamelToSnake(k)
|
||||||
|
// Handle nested maps
|
||||||
|
if nestedMap, ok := v.(map[string]interface{}); ok {
|
||||||
|
snakeDict[snakeKey] = CamelToSnakeDict(nestedMap)
|
||||||
|
} else if nestedSlice, ok := v.([]interface{}); ok {
|
||||||
|
// Handle slices of maps
|
||||||
|
var newSlice []interface{}
|
||||||
|
for _, item := range nestedSlice {
|
||||||
|
if itemMap, ok := item.(map[string]interface{}); ok {
|
||||||
|
newSlice = append(newSlice, CamelToSnakeDict(itemMap))
|
||||||
|
} else {
|
||||||
|
newSlice = append(newSlice, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
snakeDict[snakeKey] = newSlice
|
||||||
|
} else {
|
||||||
|
snakeDict[snakeKey] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return snakeDict
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatEndDate converts various date formats to time.Time
|
||||||
|
func FormatEndDate(end interface{}) time.Time {
|
||||||
|
if end == nil {
|
||||||
|
return time.Now().UTC().Truncate(24 * time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := end.(type) {
|
||||||
|
case string:
|
||||||
|
t, _ := time.Parse("2006-01-02", v)
|
||||||
|
return t
|
||||||
|
case time.Time:
|
||||||
|
return v
|
||||||
|
default:
|
||||||
|
return time.Now().UTC().Truncate(24 * time.Hour)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLocalizedDateTime converts GMT and local timestamps to localized time
|
||||||
|
func GetLocalizedDateTime(gmtTimestamp, localTimestamp int64) time.Time {
|
||||||
|
localDiff := localTimestamp - gmtTimestamp
|
||||||
|
offset := time.Duration(localDiff) * time.Millisecond
|
||||||
|
loc := time.FixedZone("", int(offset.Seconds()))
|
||||||
|
gmtTime := time.Unix(0, gmtTimestamp*int64(time.Millisecond)).UTC()
|
||||||
|
return gmtTime.In(loc)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user