diff --git a/cmd/garth/activities.go b/cmd/garth/activities.go
deleted file mode 100644
index 4eb99c2..0000000
--- a/cmd/garth/activities.go
+++ /dev/null
@@ -1,416 +0,0 @@
-package main
-
-import (
- "encoding/csv"
- "encoding/json"
- "fmt"
- "os"
- "path/filepath"
- "strconv"
- "sync"
- "time"
-
- "github.com/rodaine/table"
- "github.com/schollz/progressbar/v3"
- "github.com/spf13/cobra"
- "github.com/spf13/viper"
-
- "github.com/sstent/go-garth/pkg/garmin"
-)
-
-var (
- activitiesCmd = &cobra.Command{
- Use: "activities",
- Short: "Manage Garmin Connect activities",
- Long: `Provides commands to list, get details, search, and download Garmin Connect activities.`,
- }
-
- listActivitiesCmd = &cobra.Command{
- Use: "list",
- Short: "List recent activities",
- Long: `List recent Garmin Connect activities with optional filters.`,
- RunE: runListActivities,
- }
-
- getActivitiesCmd = &cobra.Command{
- Use: "get [activityID]",
- Short: "Get activity details",
- Long: `Get detailed information for a specific Garmin Connect activity.`,
- Args: cobra.ExactArgs(1),
- RunE: runGetActivity,
- }
-
- downloadActivitiesCmd = &cobra.Command{
- Use: "download [activityID]",
- Short: "Download activity data",
- Args: cobra.RangeArgs(0, 1), RunE: runDownloadActivity,
- }
-
- searchActivitiesCmd = &cobra.Command{
- Use: "search",
- Short: "Search activities",
- Long: `Search Garmin Connect activities by a query string.`,
- RunE: runSearchActivities,
- }
-
- // Flags for listActivitiesCmd
- activityLimit int
- activityOffset int
- activityType string
- activityDateFrom string
- activityDateTo string
-
- // Flags for downloadActivitiesCmd
- downloadFormat string
- outputDir string
- downloadOriginal bool
- downloadAll bool
-)
-
-func init() {
- rootCmd.AddCommand(activitiesCmd)
-
- activitiesCmd.AddCommand(listActivitiesCmd)
- listActivitiesCmd.Flags().IntVar(&activityLimit, "limit", 20, "Maximum number of activities to retrieve")
- listActivitiesCmd.Flags().IntVar(&activityOffset, "offset", 0, "Offset for activities list")
- listActivitiesCmd.Flags().StringVar(&activityType, "type", "", "Filter activities by type (e.g., running, cycling)")
- listActivitiesCmd.Flags().StringVar(&activityDateFrom, "from", "", "Start date for filtering activities (YYYY-MM-DD)")
- listActivitiesCmd.Flags().StringVar(&activityDateTo, "to", "", "End date for filtering activities (YYYY-MM-DD)")
-
- activitiesCmd.AddCommand(getActivitiesCmd)
-
- activitiesCmd.AddCommand(downloadActivitiesCmd)
- downloadActivitiesCmd.Flags().StringVar(&downloadFormat, "format", "gpx", "Download format (gpx, tcx, fit, csv)")
- downloadActivitiesCmd.Flags().StringVar(&outputDir, "output-dir", ".", "Output directory for downloaded files")
- downloadActivitiesCmd.Flags().BoolVar(&downloadOriginal, "original", false, "Download original uploaded file")
-
- downloadActivitiesCmd.Flags().BoolVar(&downloadAll, "all", false, "Download all activities matching filters")
- downloadActivitiesCmd.Flags().StringVar(&activityType, "type", "", "Filter activities by type (e.g., running, cycling)")
- downloadActivitiesCmd.Flags().StringVar(&activityDateFrom, "from", "", "Start date for filtering activities (YYYY-MM-DD)")
- downloadActivitiesCmd.Flags().StringVar(&activityDateTo, "to", "", "End date for filtering activities (YYYY-MM-DD)")
-
- activitiesCmd.AddCommand(searchActivitiesCmd)
- searchActivitiesCmd.Flags().StringP("query", "q", "", "Query string to search for activities")
-}
-
-func runListActivities(cmd *cobra.Command, args []string) error {
- garminClient, err := garmin.NewClient("www.garmin.com") // TODO: Domain should be configurable
- if err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- }
-
- sessionFile := "garmin_session.json" // TODO: Make session file configurable
- if err := garminClient.LoadSession(sessionFile); err != nil {
- return fmt.Errorf("not logged in: %w", err)
- }
-
- opts := garmin.ActivityOptions{
- Limit: activityLimit,
- Offset: activityOffset,
- ActivityType: activityType,
- }
-
- if activityDateFrom != "" {
- opts.DateFrom, err = time.Parse("2006-01-02", activityDateFrom)
- if err != nil {
- return fmt.Errorf("invalid date format for --from: %w", err)
- }
- }
-
- if activityDateTo != "" {
- opts.DateTo, err = time.Parse("2006-01-02", activityDateTo)
- if err != nil {
- return fmt.Errorf("invalid date format for --to: %w", err)
- }
- }
-
- activities, err := garminClient.ListActivities(opts)
- if err != nil {
- return fmt.Errorf("failed to list activities: %w", err)
- }
-
- if len(activities) == 0 {
- fmt.Println("No activities found.")
- return nil
- }
-
- outputFormat := viper.GetString("output")
-
- switch outputFormat {
- case "json":
- data, err := json.MarshalIndent(activities, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal activities to JSON: %w", err)
- }
- fmt.Println(string(data))
- case "csv":
- writer := csv.NewWriter(os.Stdout)
- defer writer.Flush()
-
- writer.Write([]string{"ActivityID", "ActivityName", "ActivityType", "StartTime", "Distance(km)", "Duration(s)"})
- for _, activity := range activities {
- writer.Write([]string{
- fmt.Sprintf("%d", activity.ActivityID),
- activity.ActivityName,
- activity.ActivityType.TypeKey,
- activity.StartTimeLocal.Format("2006-01-02 15:04:05"),
- fmt.Sprintf("%.2f", activity.Distance/1000),
- fmt.Sprintf("%.0f", activity.Duration),
- })
- }
- case "table":
- tbl := table.New("ID", "Name", "Type", "Date", "Distance (km)", "Duration (s)")
- for _, activity := range activities {
- tbl.AddRow(
- fmt.Sprintf("%d", activity.ActivityID),
- activity.ActivityName,
- activity.ActivityType.TypeKey,
- activity.StartTimeLocal.Format("2006-01-02 15:04:05"),
- fmt.Sprintf("%.2f", activity.Distance/1000),
- fmt.Sprintf("%.0f", activity.Duration),
- )
- }
- tbl.Print()
- default:
- return fmt.Errorf("unsupported output format: %s", outputFormat)
- }
-
- return nil
-}
-
-func runGetActivity(cmd *cobra.Command, args []string) error {
- activityIDStr := args[0]
- activityID, err := strconv.Atoi(activityIDStr)
- if err != nil {
- return fmt.Errorf("invalid activity ID: %w", err)
- }
-
- garminClient, err := garmin.NewClient("www.garmin.com") // TODO: Domain should be configurable
- if err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- }
-
- sessionFile := "garmin_session.json" // TODO: Make session file configurable
- if err := garminClient.LoadSession(sessionFile); err != nil {
- return fmt.Errorf("not logged in: %w", err)
- }
-
- activityDetail, err := garminClient.GetActivity(activityID)
- if err != nil {
- return fmt.Errorf("failed to get activity details: %w", err)
- }
-
- fmt.Printf("Activity Details (ID: %d):\n", activityDetail.ActivityID)
- fmt.Printf(" Name: %s\n", activityDetail.ActivityName)
- fmt.Printf(" Type: %s\n", activityDetail.ActivityType.TypeKey)
- fmt.Printf(" Date: %s\n", activityDetail.StartTimeLocal.Format("2006-01-02 15:04:05"))
- fmt.Printf(" Distance: %.2f km\n", activityDetail.Distance/1000)
- fmt.Printf(" Duration: %.0f s\n", activityDetail.Duration)
- fmt.Printf(" Description: %s\n", activityDetail.Description)
-
- return nil
-}
-
-func runDownloadActivity(cmd *cobra.Command, args []string) error {
- var wg sync.WaitGroup
- const concurrencyLimit = 5 // Limit concurrent downloads
- sem := make(chan struct{}, concurrencyLimit)
-
- garminClient, err := garmin.NewClient("www.garmin.com") // TODO: Domain should be configurable
- if err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- }
-
- sessionFile := "garmin_session.json" // TODO: Make session file configurable
- if err := garminClient.LoadSession(sessionFile); err != nil {
- return fmt.Errorf("not logged in: %w", err)
- }
-
- var activitiesToDownload []garmin.Activity
-
- if downloadAll || len(args) == 0 {
- opts := garmin.ActivityOptions{
- ActivityType: activityType,
- }
-
- if activityDateFrom != "" {
- opts.DateFrom, err = time.Parse("2006-01-02", activityDateFrom)
- if err != nil {
- return fmt.Errorf("invalid date format for --from: %w", err)
- }
- }
-
- if activityDateTo != "" {
- opts.DateTo, err = time.Parse("2006-01-02", activityDateTo)
- if err != nil {
- return fmt.Errorf("invalid date format for --to: %w", err)
- }
- }
-
- activitiesToDownload, err = garminClient.ListActivities(opts)
- if err != nil {
- return fmt.Errorf("failed to list activities for batch download: %w", err)
- }
-
- if len(activitiesToDownload) == 0 {
- fmt.Println("No activities found matching the filters for download.")
- return nil
- }
- } else if len(args) == 1 {
- activityIDStr := args[0]
- activityID, err := strconv.Atoi(activityIDStr)
- if err != nil {
- return fmt.Errorf("invalid activity ID: %w", err)
- }
- // For single download, we need to fetch the activity details to get its name and type
- activityDetail, err := garminClient.GetActivity(activityID)
- if err != nil {
- return fmt.Errorf("failed to get activity details for download: %w", err)
- }
- activitiesToDownload = []garmin.Activity{activityDetail.Activity}
- } else {
- return fmt.Errorf("invalid arguments: specify an activity ID or use --all with filters")
- }
-
- fmt.Printf("Starting download of %d activities...\n", len(activitiesToDownload))
-
- bar := progressbar.NewOptions(len(activitiesToDownload),
- progressbar.OptionEnableColorCodes(true),
- progressbar.OptionShowBytes(false),
- progressbar.OptionSetWidth(15),
- progressbar.OptionSetDescription("Downloading activities..."),
- progressbar.OptionSetTheme(progressbar.Theme{
- Saucer: "[green]=[reset]",
- SaucerPadding: " ",
- BarStart: "[ ",
- BarEnd: " ]",
- }),
- )
-
- for _, activity := range activitiesToDownload {
- wg.Add(1)
- sem <- struct{}{}
- go func(activity garmin.Activity) {
- defer wg.Done()
- defer func() { <-sem }()
-
- if downloadFormat == "csv" {
- activityDetail, err := garminClient.GetActivity(int(activity.ActivityID))
- if err != nil {
- fmt.Printf("Warning: Failed to get activity details for CSV export for activity %d: %v\n", activity.ActivityID, err)
- bar.Add(1)
- return
- }
-
- filename := fmt.Sprintf("%d.csv", activity.ActivityID)
- outputPath := filename
- if outputDir != "" {
- outputPath = filepath.Join(outputDir, filename)
- }
-
- file, err := os.Create(outputPath)
- if err != nil {
- fmt.Printf("Warning: Failed to create CSV file for activity %d: %v\n", activity.ActivityID, err)
- bar.Add(1)
- return
- }
- defer file.Close()
-
- writer := csv.NewWriter(file)
- defer writer.Flush()
-
- // Write header
- writer.Write([]string{"ActivityID", "ActivityName", "ActivityType", "StartTime", "Distance(km)", "Duration(s)", "Description"})
-
- // Write data
- writer.Write([]string{
- fmt.Sprintf("%d", activityDetail.ActivityID),
- activityDetail.ActivityName,
- activityDetail.ActivityType.TypeKey,
- activityDetail.StartTimeLocal.Format("2006-01-02 15:04:05"),
- fmt.Sprintf("%.2f", activityDetail.Distance/1000),
- fmt.Sprintf("%.0f", activityDetail.Duration),
- activityDetail.Description,
- })
-
- fmt.Printf("Activity %d summary exported to %s\n", activity.ActivityID, outputPath)
- } else {
- filename := fmt.Sprintf("%d.%s", activity.ActivityID, downloadFormat)
- if downloadOriginal {
- filename = fmt.Sprintf("%d_original.fit", activity.ActivityID) // Assuming original is .fit
- }
- outputPath := filepath.Join(outputDir, filename)
-
- // Check if file already exists
- if _, err := os.Stat(outputPath); err == nil {
- fmt.Printf("Skipping activity %d: file already exists at %s\n", activity.ActivityID, outputPath)
- bar.Add(1)
- return
- } else if !os.IsNotExist(err) {
- fmt.Printf("Warning: Failed to check existence of file %s for activity %d: %v\n", outputPath, activity.ActivityID, err)
- bar.Add(1)
- return
- }
-
- opts := garmin.DownloadOptions{
- Format: downloadFormat,
- OutputDir: outputDir,
- Original: downloadOriginal,
- Filename: filename, // Pass filename to opts
- }
-
- fmt.Printf("Downloading activity %d in %s format to %s...\n", activity.ActivityID, downloadFormat, outputPath)
- if err := garminClient.DownloadActivity(int(activity.ActivityID), opts); err != nil {
- fmt.Printf("Warning: Failed to download activity %d: %v\n", activity.ActivityID, err)
- bar.Add(1)
- return
- }
-
- fmt.Printf("Activity %d downloaded successfully.\n", activity.ActivityID)
- }
- bar.Add(1)
- }(activity)
- }
-
- wg.Wait()
- bar.Finish()
- fmt.Println("All downloads finished.")
-
- return nil
-}
-
-func runSearchActivities(cmd *cobra.Command, args []string) error {
- query, err := cmd.Flags().GetString("query")
- if err != nil || query == "" {
- return fmt.Errorf("search query cannot be empty")
- }
-
- garminClient, err := garmin.NewClient("www.garmin.com") // TODO: Domain should be configurable
- if err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- }
-
- sessionFile := "garmin_session.json" // TODO: Make session file configurable
- if err := garminClient.LoadSession(sessionFile); err != nil {
- return fmt.Errorf("not logged in: %w", err)
- }
-
- activities, err := garminClient.SearchActivities(query)
- if err != nil {
- return fmt.Errorf("failed to search activities: %w", err)
- }
-
- if len(activities) == 0 {
- fmt.Printf("No activities found for query '%s'.\n", query)
- return nil
- }
-
- fmt.Printf("Activities matching '%s':\n", query)
- for _, activity := range activities {
- fmt.Printf("- ID: %d, Name: %s, Type: %s, Date: %s\n",
- activity.ActivityID, activity.ActivityName, activity.ActivityType.TypeKey,
- activity.StartTimeLocal.Format("2006-01-02"))
- }
-
- return nil
-}
diff --git a/cmd/garth/auth.go b/cmd/garth/auth.go
deleted file mode 100644
index c2bed2d..0000000
--- a/cmd/garth/auth.go
+++ /dev/null
@@ -1,183 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
-
- "golang.org/x/term"
-
- "github.com/sstent/go-garth/pkg/garmin"
-
- "github.com/spf13/cobra"
-)
-
-var (
- authCmd = &cobra.Command{
- Use: "auth",
- Short: "Authentication management",
- Long: `Manage authentication with Garmin Connect, including login, logout, and status.`,
- }
-
- loginCmd = &cobra.Command{
- Use: "login",
- Short: "Login to Garmin Connect",
- Long: `Login to Garmin Connect interactively or using provided credentials.`,
- RunE: runLogin,
- }
-
- logoutCmd = &cobra.Command{
- Use: "logout",
- Short: "Logout from Garmin Connect",
- Long: `Clear the current Garmin Connect session.`,
- RunE: runLogout,
- }
-
- statusCmd = &cobra.Command{
- Use: "status",
- Short: "Show Garmin Connect authentication status",
- Long: `Display the current authentication status and session information.`,
- RunE: runStatus,
- }
-
- refreshCmd = &cobra.Command{
- Use: "refresh",
- Short: "Refresh Garmin Connect session tokens",
- Long: `Refresh the authentication tokens for the current Garmin Connect session.`,
- RunE: runRefresh,
- }
-
- loginEmail string
- loginPassword string
- passwordStdinFlag bool
-)
-
-func init() {
- rootCmd.AddCommand(authCmd)
-
- authCmd.AddCommand(loginCmd)
- loginCmd.Flags().StringVarP(&loginEmail, "email", "e", "", "Email for Garmin Connect login")
- loginCmd.Flags().BoolVarP(&passwordStdinFlag, "password-stdin", "p", false, "Read password from stdin")
-
- authCmd.AddCommand(logoutCmd)
- authCmd.AddCommand(statusCmd)
- authCmd.AddCommand(refreshCmd)
-}
-
-func runLogin(cmd *cobra.Command, args []string) error {
- var email, password string
- var err error
-
- if loginEmail != "" {
- email = loginEmail
- } else {
- fmt.Print("Enter Garmin Connect email: ")
- _, err = fmt.Scanln(&email)
- if err != nil {
- return fmt.Errorf("failed to read email: %w", err)
- }
- }
-
- if passwordStdinFlag {
- fmt.Print("Enter password: ")
- passwordBytes, err := term.ReadPassword(int(os.Stdin.Fd()))
- if err != nil {
- return fmt.Errorf("failed to read password from stdin: %w", err)
- }
- password = string(passwordBytes)
- fmt.Println() // Newline after password input
- } else {
- fmt.Print("Enter password: ")
- passwordBytes, err := term.ReadPassword(int(os.Stdin.Fd()))
- if err != nil {
- return fmt.Errorf("failed to read password: %w", err)
- }
- password = string(passwordBytes)
- fmt.Println() // Newline after password input
- }
-
- // Create client
- // TODO: Domain should be configurable
- garminClient, err := garmin.NewClient("www.garmin.com")
- if err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- }
-
- // Try to load existing session first
- sessionFile := "garmin_session.json" // TODO: Make session file configurable
- if err := garminClient.LoadSession(sessionFile); err != nil {
- fmt.Println("No existing session found or session invalid, logging in with credentials...")
-
- if err := garminClient.Login(email, password); err != nil {
- return fmt.Errorf("login failed: %w", err)
- }
-
- // Save session for future use
- if err := garminClient.SaveSession(sessionFile); err != nil {
- fmt.Printf("Failed to save session: %v\n", err)
- }
- } else {
- fmt.Println("Loaded existing session")
- }
-
- fmt.Println("Login successful!")
- return nil
-}
-
-func runLogout(cmd *cobra.Command, args []string) error {
- sessionFile := "garmin_session.json" // TODO: Make session file configurable
-
- if _, err := os.Stat(sessionFile); os.IsNotExist(err) {
- fmt.Println("No active session to log out from.")
- return nil
- }
-
- if err := os.Remove(sessionFile); err != nil {
- return fmt.Errorf("failed to remove session file: %w", err)
- }
-
- fmt.Println("Logged out successfully. Session cleared.")
- return nil
-}
-
-func runStatus(cmd *cobra.Command, args []string) error {
- sessionFile := "garmin_session.json" // TODO: Make session file configurable
-
- garminClient, err := garmin.NewClient("www.garmin.com") // TODO: Domain should be configurable
- if err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- }
-
- if err := garminClient.LoadSession(sessionFile); err != nil {
- fmt.Println("Not logged in or session expired.")
- return nil
- }
-
- fmt.Println("Logged in. Session is active.")
- // TODO: Add more detailed status information, e.g., session expiry
- return nil
-}
-
-func runRefresh(cmd *cobra.Command, args []string) error {
- sessionFile := "garmin_session.json" // TODO: Make session file configurable
-
- garminClient, err := garmin.NewClient("www.garmin.com") // TODO: Domain should be configurable
- if err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- }
-
- if err := garminClient.LoadSession(sessionFile); err != nil {
- return fmt.Errorf("cannot refresh: no active session found: %w", err)
- }
-
- fmt.Println("Attempting to refresh session...")
- if err := garminClient.RefreshSession(); err != nil {
- return fmt.Errorf("failed to refresh session: %w", err)
- }
-
- if err := garminClient.SaveSession(sessionFile); err != nil {
- fmt.Printf("Failed to save refreshed session: %v\n", err)
- }
-
- fmt.Println("Session refreshed successfully.")
- return nil
-}
diff --git a/cmd/garth/cmd/activities.go b/cmd/garth/cmd/activities.go
deleted file mode 100644
index 185e19b..0000000
--- a/cmd/garth/cmd/activities.go
+++ /dev/null
@@ -1,67 +0,0 @@
-package cmd
-
-import (
- "fmt"
- "log"
- "time"
-
- "github.com/sstent/go-garth/internal/auth/credentials"
- "github.com/sstent/go-garth/pkg/garmin"
-
- "github.com/spf13/cobra"
-)
-
-var activitiesCmd = &cobra.Command{
- Use: "activities",
- Short: "Display recent Garmin Connect activities",
- Long: `Fetches and displays a list of recent activities from Garmin Connect.`,
- Run: func(cmd *cobra.Command, args []string) {
- // Load credentials from .env file
- _, _, domain, err := credentials.LoadEnvCredentials()
- if err != nil {
- log.Fatalf("Failed to load credentials: %v", err)
- }
-
- // Create client
- garminClient, err := garmin.NewClient(domain)
- if err != nil {
- log.Fatalf("Failed to create client: %v", err)
- }
-
- // Try to load existing session first
- sessionFile := "garmin_session.json"
- if err := garminClient.LoadSession(sessionFile); err != nil {
- log.Fatalf("No existing session found. Please run 'garth login' first.")
- }
-
- opts := garmin.ActivityOptions{
- Limit: 5,
- }
- activities, err := garminClient.ListActivities(opts)
- if err != nil {
- log.Fatalf("Failed to get activities: %v", err)
- }
- displayActivities(activities)
- },
-}
-
-func init() {
- rootCmd.AddCommand(activitiesCmd)
-}
-
-func displayActivities(activities []garmin.Activity) {
- fmt.Printf("\n=== Recent Activities ===\n")
- for i, activity := range activities {
- fmt.Printf("%d. %s\n", i+1, activity.ActivityName)
- fmt.Printf(" Type: %s\n", activity.ActivityType.TypeKey)
- fmt.Printf(" Date: %s\n", activity.StartTimeLocal)
- if activity.Distance > 0 {
- fmt.Printf(" Distance: %.2f km\n", activity.Distance/1000)
- }
- if activity.Duration > 0 {
- duration := time.Duration(activity.Duration) * time.Second
- fmt.Printf(" Duration: %v\n", duration.Round(time.Second))
- }
- fmt.Println()
- }
-}
diff --git a/cmd/garth/cmd/data.go b/cmd/garth/cmd/data.go
deleted file mode 100644
index e6d1499..0000000
--- a/cmd/garth/cmd/data.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package cmd
-
-import (
- "encoding/json"
- "fmt"
- "log"
- "os"
- "time"
-
- "github.com/sstent/go-garth/internal/auth/credentials"
- "github.com/sstent/go-garth/pkg/garmin"
-
- "github.com/spf13/cobra"
-)
-
-var (
- dataDateStr string
- dataDays int
- dataOutputFile string
-)
-
-var dataCmd = &cobra.Command{
- Use: "data [type]",
- Short: "Fetch various data types from Garmin Connect",
- Long: `Fetch data such as bodybattery, sleep, HRV, and weight from Garmin Connect.`,
- Args: cobra.ExactArgs(1), // Expects one argument: the data type
- Run: func(cmd *cobra.Command, args []string) {
- dataType := args[0]
-
- // Load credentials from .env file
- _, _, domain, err := credentials.LoadEnvCredentials()
- if err != nil {
- log.Fatalf("Failed to load credentials: %v", err)
- }
-
- // Create client
- garminClient, err := garmin.NewClient(domain)
- if err != nil {
- log.Fatalf("Failed to create client: %v", err)
- }
-
- // Try to load existing session first
- sessionFile := "garmin_session.json"
- if err := garminClient.LoadSession(sessionFile); err != nil {
- log.Fatalf("No existing session found. Please run 'garth login' first.")
- }
-
- endDate := time.Now().AddDate(0, 0, -1) // default to yesterday
- if dataDateStr != "" {
- parsedDate, err := time.Parse("2006-01-02", dataDateStr)
- if err != nil {
- log.Fatalf("Invalid date format: %v", err)
- }
- endDate = parsedDate
- }
-
- var result interface{}
-
- switch dataType {
- case "bodybattery":
- result, err = garminClient.GetBodyBatteryData(endDate)
- case "sleep":
- result, err = garminClient.GetSleepData(endDate)
- case "hrv":
- result, err = garminClient.GetHrvData(endDate)
- // case "weight":
- // result, err = garminClient.GetWeight(endDate)
- default:
- log.Fatalf("Unknown data type: %s", dataType)
- }
-
- if err != nil {
- log.Fatalf("Failed to get %s data: %v", dataType, err)
- }
-
- outputResult(result, dataOutputFile)
- },
-}
-
-func init() {
- rootCmd.AddCommand(dataCmd)
-
- dataCmd.Flags().StringVar(&dataDateStr, "date", "", "Date in YYYY-MM-DD format (default: yesterday)")
- dataCmd.Flags().StringVar(&dataOutputFile, "output", "", "Output file for JSON results")
- // dataCmd.Flags().IntVar(&dataDays, "days", 1, "Number of days to fetch") // Not used for single day data types
-}
-
-func outputResult(data interface{}, outputFile string) {
- jsonBytes, err := json.MarshalIndent(data, "", " ")
- if err != nil {
- log.Fatalf("Failed to marshal result: %v", err)
- }
-
- if outputFile != "" {
- if err := os.WriteFile(outputFile, jsonBytes, 0644); err != nil {
- log.Fatalf("Failed to write output file: %v", err)
- }
- fmt.Printf("Results saved to %s\n", outputFile)
- } else {
- fmt.Println(string(jsonBytes))
- }
-}
diff --git a/cmd/garth/cmd/root.go b/cmd/garth/cmd/root.go
deleted file mode 100644
index ecc2f15..0000000
--- a/cmd/garth/cmd/root.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package cmd
-
-import (
- "fmt"
- "os"
-
- "github.com/spf13/cobra"
-)
-
-var rootCmd = &cobra.Command{
- Use: "garth",
- Short: "garth is a CLI for interacting with Garmin Connect",
- Long: `A command-line interface for Garmin Connect that allows you to
-interact with your health and fitness data.`,
- // Uncomment the following line if your bare application
- // has an action associated with it:
- // Run: func(cmd *cobra.Command, args []string) { },
-}
-
-// Execute adds all child commands to the root command and sets flags appropriately.
-// This is called by main.main(). It only needs to happen once to the rootCmd.
-func Execute() {
- if err := rootCmd.Execute(); err != nil {
- fmt.Fprintf(os.Stderr, "Error: %v\n", err)
- os.Exit(1)
- }
-}
-
-func init() {
- // Here you will define your flags and configuration settings.
- // Cobra supports persistent flags, which, if defined here, will be global for your application.
-
- // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.garth.yaml)")
-
- // Cobra also supports local flags, which will only run when this action is called directly.
- // rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-}
-
diff --git a/cmd/garth/cmd/stats.go b/cmd/garth/cmd/stats.go
deleted file mode 100644
index 505249c..0000000
--- a/cmd/garth/cmd/stats.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package cmd
-
-import (
- "log"
- "time"
-
- "github.com/sstent/go-garth/internal/auth/credentials"
- "github.com/sstent/go-garth/pkg/garmin"
-
- "github.com/spf13/cobra"
-)
-
-var (
- statsDateStr string
- statsDays int
- statsOutputFile string
-)
-
-var statsCmd = &cobra.Command{
- Use: "stats [type]",
- Short: "Fetch various stats types from Garmin Connect",
- Long: `Fetch stats such as steps, stress, hydration, intensity, sleep, and HRV from Garmin Connect.`,
- Args: cobra.ExactArgs(1), // Expects one argument: the stats type
- Run: func(cmd *cobra.Command, args []string) {
- statsType := args[0]
-
- // Load credentials from .env file
- _, _, domain, err := credentials.LoadEnvCredentials()
- if err != nil {
- log.Fatalf("Failed to load credentials: %v", err)
- }
-
- // Create client
- garminClient, err := garmin.NewClient(domain)
- if err != nil {
- log.Fatalf("Failed to create client: %v", err)
- }
-
- // Try to load existing session first
- sessionFile := "garmin_session.json"
- if err := garminClient.LoadSession(sessionFile); err != nil {
- log.Fatalf("No existing session found. Please run 'garth login' first.")
- }
-
- endDate := time.Now().AddDate(0, 0, -1) // default to yesterday
- if statsDateStr != "" {
- parsedDate, err := time.Parse("2006-01-02", statsDateStr)
- if err != nil {
- log.Fatalf("Invalid date format: %v", err)
- }
- endDate = parsedDate
- }
-
- var stats garmin.Stats
- switch statsType {
- case "steps":
- stats = garmin.NewDailySteps()
- case "stress":
- stats = garmin.NewDailyStress()
- case "hydration":
- stats = garmin.NewDailyHydration()
- case "intensity":
- stats = garmin.NewDailyIntensityMinutes()
- case "sleep":
- stats = garmin.NewDailySleep()
- case "hrv":
- stats = garmin.NewDailyHRV()
- default:
- log.Fatalf("Unknown stats type: %s", statsType)
- }
-
- result, err := stats.List(endDate, statsDays, garminClient.Client)
- if err != nil {
- log.Fatalf("Failed to get %s stats: %v", statsType, err)
- }
-
- outputResult(result, statsOutputFile)
- },
-}
-
-func init() {
- rootCmd.AddCommand(statsCmd)
-
- statsCmd.Flags().StringVar(&statsDateStr, "date", "", "Date in YYYY-MM-DD format (default: yesterday)")
- statsCmd.Flags().IntVar(&statsDays, "days", 1, "Number of days to fetch")
- statsCmd.Flags().StringVar(&statsOutputFile, "output", "", "Output file for JSON results")
-}
diff --git a/cmd/garth/cmd/tokens.go b/cmd/garth/cmd/tokens.go
deleted file mode 100644
index 4e1accf..0000000
--- a/cmd/garth/cmd/tokens.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package cmd
-
-import (
- "encoding/json"
- "fmt"
- "log"
-
- "github.com/sstent/go-garth/internal/auth/credentials"
- "github.com/sstent/go-garth/pkg/garmin"
-
- "github.com/spf13/cobra"
-)
-
-var tokensCmd = &cobra.Command{
- Use: "tokens",
- Short: "Output OAuth tokens in JSON format",
- Long: `Output the OAuth1 and OAuth2 tokens in JSON format after a successful login.`,
- Run: func(cmd *cobra.Command, args []string) {
- // Load credentials from .env file
- _, _, domain, err := credentials.LoadEnvCredentials()
- if err != nil {
- log.Fatalf("Failed to load credentials: %v", err)
- }
-
- // Create client
- garminClient, err := garmin.NewClient(domain)
- if err != nil {
- log.Fatalf("Failed to create client: %v", err)
- }
-
- // Try to load existing session first
- sessionFile := "garmin_session.json"
- if err := garminClient.LoadSession(sessionFile); err != nil {
- log.Fatalf("No existing session found. Please run 'garth login' first.")
- }
-
- tokens := struct {
- OAuth1 *garmin.OAuth1Token `json:"oauth1"`
- OAuth2 *garmin.OAuth2Token `json:"oauth2"`
- }{
- OAuth1: garminClient.OAuth1Token(),
- OAuth2: garminClient.OAuth2Token(),
- }
-
- jsonBytes, err := json.MarshalIndent(tokens, "", " ")
- if err != nil {
- log.Fatalf("Failed to marshal tokens: %v", err)
- }
- fmt.Println(string(jsonBytes))
- },
-}
-
-func init() {
- rootCmd.AddCommand(tokensCmd)
-}
diff --git a/cmd/garth/config.go b/cmd/garth/config.go
deleted file mode 100644
index a11dcdb..0000000
--- a/cmd/garth/config.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package main
-
-import (
- "fmt"
- "path/filepath"
-
- "github.com/spf13/cobra"
- "gopkg.in/yaml.v3"
-
- "github.com/sstent/go-garth/internal/config"
-)
-
-func init() {
- rootCmd.AddCommand(configCmd)
- configCmd.AddCommand(configInitCmd)
- configCmd.AddCommand(configShowCmd)
-}
-
-var configCmd = &cobra.Command{
- Use: "config",
- Short: "Manage garth configuration",
- Long: `Allows you to initialize, show, and manage garth's configuration file.`,
-}
-
-var configInitCmd = &cobra.Command{
- Use: "init",
- Short: "Initialize a default config file",
- Long: `Creates a default garth configuration file in the standard location ($HOME/.config/garth/config.yaml) if one does not already exist.`,
- RunE: func(cmd *cobra.Command, args []string) error {
- configPath := filepath.Join(config.UserConfigDir(), "config.yaml")
- _, err := config.InitConfig(configPath)
- if err != nil {
- return fmt.Errorf("error initializing config: %w", err)
- }
- fmt.Printf("Default config file initialized at: %s\n", configPath)
- return nil
- },
-}
-
-var configShowCmd = &cobra.Command{
- Use: "show",
- Short: "Show the current configuration",
- Long: `Displays the currently loaded garth configuration, including values from the config file and environment variables.`,
- RunE: func(cmd *cobra.Command, args []string) error {
- if cfg == nil {
- return fmt.Errorf("configuration not loaded")
- }
-
- data, err := yaml.Marshal(cfg)
- if err != nil {
- return fmt.Errorf("error marshaling config to YAML: %w", err)
- }
- fmt.Println(string(data))
- return nil
- },
-}
\ No newline at end of file
diff --git a/cmd/garth/health.go b/cmd/garth/health.go
deleted file mode 100644
index 1c7182f..0000000
--- a/cmd/garth/health.go
+++ /dev/null
@@ -1,911 +0,0 @@
-package main
-
-import (
- "encoding/csv"
- "encoding/json"
- "fmt"
- "os"
- "strconv"
- "time"
-
- "github.com/rodaine/table"
- "github.com/spf13/cobra"
- "github.com/spf13/viper"
-
- "github.com/sstent/go-garth/internal/data" // Import the data package
- types "github.com/sstent/go-garth/internal/models/types"
- "github.com/sstent/go-garth/pkg/garmin"
-)
-
-var (
- healthCmd = &cobra.Command{
- Use: "health",
- Short: "Manage Garmin Connect health data",
- Long: `Provides commands to fetch various health metrics like sleep, HRV, stress, and body battery.`,
- }
-
- sleepCmd = &cobra.Command{
- Use: "sleep",
- Short: "Get sleep data",
- Long: `Fetch sleep data for a specified date range.`,
- RunE: runSleep,
- }
-
- hrvCmd = &cobra.Command{
- Use: "hrv",
- Short: "Get HRV data",
- Long: `Fetch Heart Rate Variability (HRV) data.`,
- RunE: runHrv,
- }
-
- stressCmd = &cobra.Command{
- Use: "stress",
- Short: "Get stress data",
- Long: `Fetch stress data.`,
- RunE: runStress,
- }
-
- bodyBatteryCmd = &cobra.Command{
- Use: "bodybattery",
- Short: "Get Body Battery data",
- Long: `Fetch Body Battery data.`,
- RunE: runBodyBattery,
- }
-
- vo2maxCmd = &cobra.Command{
- Use: "vo2max",
- Short: "Get VO2 Max data",
- Long: `Fetch VO2 Max data for a specified date range.`,
- RunE: runVO2Max,
- }
-
- hrZonesCmd = &cobra.Command{
- Use: "hr-zones",
- Short: "Get Heart Rate Zones data",
- Long: `Fetch Heart Rate Zones data.`,
- RunE: runHRZones,
- }
-
- trainingStatusCmd = &cobra.Command{
- Use: "training-status",
- Short: "Get Training Status data",
- Long: `Fetch Training Status data.`,
- RunE: runTrainingStatus,
- }
-
- trainingLoadCmd = &cobra.Command{
- Use: "training-load",
- Short: "Get Training Load data",
- Long: `Fetch Training Load data.`,
- RunE: runTrainingLoad,
- }
-
- fitnessAgeCmd = &cobra.Command{
- Use: "fitness-age",
- Short: "Get Fitness Age data",
- Long: `Fetch Fitness Age data.`,
- RunE: runFitnessAge,
- }
-
- wellnessCmd = &cobra.Command{
- Use: "wellness",
- Short: "Get comprehensive wellness data",
- Long: `Fetch comprehensive wellness data including body composition and resting heart rate trends.`,
- RunE: runWellness,
- }
-
- healthDateFrom string
- healthDateTo string
- healthDays int
- healthWeek bool
- healthYesterday bool
- healthAggregate string
-)
-
-func init() {
- rootCmd.AddCommand(healthCmd)
-
- healthCmd.AddCommand(sleepCmd)
- sleepCmd.Flags().StringVar(&healthDateFrom, "from", "", "Start date for data fetching (YYYY-MM-DD)")
- sleepCmd.Flags().StringVar(&healthDateTo, "to", "", "End date for data fetching (YYYY-MM-DD)")
- sleepCmd.Flags().StringVar(&healthAggregate, "aggregate", "", "Aggregate data by (day, week, month, year)")
-
- healthCmd.AddCommand(hrvCmd)
- hrvCmd.Flags().IntVar(&healthDays, "days", 0, "Number of past days to fetch data for")
- hrvCmd.Flags().StringVar(&healthAggregate, "aggregate", "", "Aggregate data by (day, week, month, year)")
-
- healthCmd.AddCommand(stressCmd)
- stressCmd.Flags().BoolVar(&healthWeek, "week", false, "Fetch data for the current week")
- stressCmd.Flags().StringVar(&healthAggregate, "aggregate", "", "Aggregate data by (day, week, month, year)")
-
- healthCmd.AddCommand(bodyBatteryCmd)
- bodyBatteryCmd.Flags().BoolVar(&healthYesterday, "yesterday", false, "Fetch data for yesterday")
- bodyBatteryCmd.Flags().StringVar(&healthAggregate, "aggregate", "", "Aggregate data by (day, week, month, year)")
-
- healthCmd.AddCommand(vo2maxCmd)
- vo2maxCmd.Flags().StringVar(&healthDateFrom, "from", "", "Start date for data fetching (YYYY-MM-DD)")
- vo2maxCmd.Flags().StringVar(&healthDateTo, "to", "", "End date for data fetching (YYYY-MM-DD)")
- vo2maxCmd.Flags().StringVar(&healthAggregate, "aggregate", "", "Aggregate data by (day, week, month, year)")
-
- healthCmd.AddCommand(hrZonesCmd)
-
- healthCmd.AddCommand(trainingStatusCmd)
- trainingStatusCmd.Flags().StringVar(&healthDateFrom, "from", "", "Date for data fetching (YYYY-MM-DD, defaults to today)")
-
- healthCmd.AddCommand(trainingLoadCmd)
- trainingLoadCmd.Flags().StringVar(&healthDateFrom, "from", "", "Date for data fetching (YYYY-MM-DD, defaults to today)")
-
- healthCmd.AddCommand(fitnessAgeCmd)
-
- healthCmd.AddCommand(wellnessCmd)
- wellnessCmd.Flags().StringVar(&healthDateFrom, "from", "", "Start date for data fetching (YYYY-MM-DD)")
- wellnessCmd.Flags().StringVar(&healthDateTo, "to", "", "End date for data fetching (YYYY-MM-DD)")
- wellnessCmd.Flags().StringVar(&healthAggregate, "aggregate", "", "Aggregate data by (day, week, month, year)")
-}
-
-func runSleep(cmd *cobra.Command, args []string) error {
- garminClient, err := garmin.NewClient(viper.GetString("domain"))
- if err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- }
-
- if err := garminClient.LoadSession(viper.GetString("session_file")); err != nil {
- return fmt.Errorf("not logged in: %w", err)
- }
-
- var startDate, endDate time.Time
-
- if healthDateFrom != "" {
- startDate, err = time.Parse("2006-01-02", healthDateFrom)
- if err != nil {
- return fmt.Errorf("invalid date format for --from: %w", err)
- }
- } else {
- startDate = time.Now().AddDate(0, 0, -7) // Default to last 7 days
- }
-
- if healthDateTo != "" {
- endDate, err = time.Parse("2006-01-02", healthDateTo)
- if err != nil {
- return fmt.Errorf("invalid date format for --to: %w", err)
- }
- } else {
- endDate = time.Now() // Default to today
- }
-
- var allSleepData []*data.DetailedSleepDataWithMethods
- for d := startDate; !d.After(endDate); d = d.AddDate(0, 0, 1) {
- // Create a new instance of DetailedSleepDataWithMethods for each day
- sleepDataFetcher := &data.DetailedSleepDataWithMethods{}
- sleepData, err := sleepDataFetcher.Get(d, garminClient.InternalClient())
- if err != nil {
- return fmt.Errorf("failed to get sleep data for %s: %w", d.Format("2006-01-02"), err)
- }
- if sleepData != nil {
- // Type assert the result back to DetailedSleepDataWithMethods
- if sdm, ok := sleepData.(*data.DetailedSleepDataWithMethods); ok {
- allSleepData = append(allSleepData, sdm)
- } else {
- return fmt.Errorf("unexpected type returned for sleep data: %T", sleepData)
- }
- }
- }
-
- if len(allSleepData) == 0 {
- fmt.Println("No sleep data found.")
- return nil
- }
-
- outputFormat := viper.GetString("output.format")
-
- switch outputFormat {
- case "json":
- data, err := json.MarshalIndent(allSleepData, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal sleep data to JSON: %w", err)
- }
- fmt.Println(string(data))
- case "csv":
- writer := csv.NewWriter(os.Stdout)
- defer writer.Flush()
-
- writer.Write([]string{"Date", "SleepScore", "TotalSleep", "Deep", "Light", "REM", "Awake", "AvgSpO2", "LowestSpO2", "AvgRespiration"})
- for _, data := range allSleepData {
- writer.Write([]string{
- data.CalendarDate.Format("2006-01-02"),
- fmt.Sprintf("%d", data.SleepScores.Overall),
- (time.Duration(data.DeepSleepSeconds+data.LightSleepSeconds+data.RemSleepSeconds) * time.Second).String(),
- (time.Duration(data.DeepSleepSeconds) * time.Second).String(),
- (time.Duration(data.LightSleepSeconds) * time.Second).String(),
- (time.Duration(data.RemSleepSeconds) * time.Second).String(),
- (time.Duration(data.AwakeSleepSeconds) * time.Second).String(),
- func() string {
- if data.AverageSpO2Value != nil {
- return fmt.Sprintf("%.2f", *data.AverageSpO2Value)
- }
- return "N/A"
- }(),
- func() string {
- if data.LowestSpO2Value != nil {
- return fmt.Sprintf("%d", *data.LowestSpO2Value)
- }
- return "N/A"
- }(),
- func() string {
- if data.AverageRespirationValue != nil {
- return fmt.Sprintf("%.2f", *data.AverageRespirationValue)
- }
- return "N/A"
- }(),
- })
- }
- case "table":
- tbl := table.New("Date", "Score", "Total Sleep", "Deep", "Light", "REM", "Awake", "Avg SpO2", "Lowest SpO2", "Avg Resp")
- for _, data := range allSleepData {
- tbl.AddRow(
- data.CalendarDate.Format("2006-01-02"),
- fmt.Sprintf("%d", data.SleepScores.Overall),
- (time.Duration(data.DeepSleepSeconds+data.LightSleepSeconds+data.RemSleepSeconds) * time.Second).String(),
- (time.Duration(data.DeepSleepSeconds) * time.Second).String(),
- (time.Duration(data.LightSleepSeconds) * time.Second).String(),
- (time.Duration(data.RemSleepSeconds) * time.Second).String(),
- (time.Duration(data.AwakeSleepSeconds) * time.Second).String(),
- func() string {
- if data.AverageSpO2Value != nil {
- return fmt.Sprintf("%.2f", *data.AverageSpO2Value)
- }
- return "N/A"
- }(),
- func() string {
- if data.LowestSpO2Value != nil {
- return fmt.Sprintf("%d", *data.LowestSpO2Value)
- }
- return "N/A"
- }(),
- func() string {
- if data.AverageRespirationValue != nil {
- return fmt.Sprintf("%.2f", *data.AverageRespirationValue)
- }
- return "N/A"
- }(),
- )
- }
- tbl.Print()
- default:
- return fmt.Errorf("unsupported output format: %s", outputFormat)
- }
-
- return nil
-}
-
-func runHrv(cmd *cobra.Command, args []string) error {
- garminClient, err := garmin.NewClient(viper.GetString("domain"))
- if err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- }
-
- if err := garminClient.LoadSession(viper.GetString("session_file")); err != nil {
- return fmt.Errorf("not logged in: %w", err)
- }
-
- days := healthDays
- if days == 0 {
- days = 7 // Default to 7 days if not specified
- }
-
- var allHrvData []*data.DailyHRVDataWithMethods
- for d := time.Now().AddDate(0, 0, -days+1); !d.After(time.Now()); d = d.AddDate(0, 0, 1) {
- hrvDataFetcher := &data.DailyHRVDataWithMethods{}
- hrvData, err := hrvDataFetcher.Get(d, garminClient.InternalClient())
- if err != nil {
- return fmt.Errorf("failed to get HRV data for %s: %w", d.Format("2006-01-02"), err)
- }
- if hrvData != nil {
- if hdm, ok := hrvData.(*data.DailyHRVDataWithMethods); ok {
- allHrvData = append(allHrvData, hdm)
- } else {
- return fmt.Errorf("unexpected type returned for HRV data: %T", hrvData)
- }
- }
- }
-
- if len(allHrvData) == 0 {
- fmt.Println("No HRV data found.")
- return nil
- }
-
- outputFormat := viper.GetString("output.format")
-
- switch outputFormat {
- case "json":
- data, err := json.MarshalIndent(allHrvData, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal HRV data to JSON: %w", err)
- }
- fmt.Println(string(data))
- case "csv":
- writer := csv.NewWriter(os.Stdout)
- defer writer.Flush()
-
- writer.Write([]string{"Date", "WeeklyAvg", "LastNightAvg", "Status", "Feedback"})
- for _, data := range allHrvData {
- writer.Write([]string{
- data.CalendarDate.Format("2006-01-02"),
- func() string {
- if data.WeeklyAvg != nil {
- return fmt.Sprintf("%.2f", *data.WeeklyAvg)
- }
- return "N/A"
- }(),
- func() string {
- if data.LastNightAvg != nil {
- return fmt.Sprintf("%.2f", *data.LastNightAvg)
- }
- return "N/A"
- }(),
- data.Status,
- data.FeedbackPhrase,
- })
- }
- case "table":
- tbl := table.New("Date", "Weekly Avg", "Last Night Avg", "Status", "Feedback")
- for _, data := range allHrvData {
- tbl.AddRow(
- data.CalendarDate.Format("2006-01-02"),
- func() string {
- if data.WeeklyAvg != nil {
- return fmt.Sprintf("%.2f", *data.WeeklyAvg)
- }
- return "N/A"
- }(),
- func() string {
- if data.LastNightAvg != nil {
- return fmt.Sprintf("%.2f", *data.LastNightAvg)
- }
- return "N/A"
- }(),
- data.Status,
- data.FeedbackPhrase,
- )
- }
- tbl.Print()
- default:
- return fmt.Errorf("unsupported output format: %s", outputFormat)
- }
-
- return nil
-}
-
-func runStress(cmd *cobra.Command, args []string) error {
- garminClient, err := garmin.NewClient(viper.GetString("domain"))
- if err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- }
-
- if err := garminClient.LoadSession(viper.GetString("session_file")); err != nil {
- return fmt.Errorf("not logged in: %w", err)
- }
-
- var startDate, endDate time.Time
- if healthWeek {
- now := time.Now()
- weekday := now.Weekday()
- // Calculate the start of the current week (Sunday)
- startDate = now.AddDate(0, 0, -int(weekday))
- endDate = startDate.AddDate(0, 0, 6) // End of the current week (Saturday)
- } else {
- // Default to today if no specific range or week is given
- startDate = time.Now()
- endDate = time.Now()
- }
-
- stressData, err := garminClient.GetStressData(startDate, endDate)
- if err != nil {
- return fmt.Errorf("failed to get stress data: %w", err)
- }
-
- if len(stressData) == 0 {
- fmt.Println("No stress data found.")
- return nil
- }
-
- // Apply aggregation if requested
- if healthAggregate != "" {
- aggregatedStress := make(map[string]struct {
- StressLevel int
- RestStressLevel int
- Count int
- })
-
- for _, data := range stressData {
- key := ""
- switch healthAggregate {
- case "day":
- key = data.Date.Format("2006-01-02")
- case "week":
- year, week := data.Date.ISOWeek()
- key = fmt.Sprintf("%d-W%02d", year, week)
- case "month":
- key = data.Date.Format("2006-01")
- case "year":
- key = data.Date.Format("2006")
- default:
- return fmt.Errorf("unsupported aggregation period: %s", healthAggregate)
- }
-
- entry := aggregatedStress[key]
- entry.StressLevel += data.StressLevel
- entry.RestStressLevel += data.RestStressLevel
- entry.Count++
- aggregatedStress[key] = entry
- }
-
- // Convert aggregated data back to a slice for output
- stressData = []types.StressData{}
- for key, entry := range aggregatedStress {
- stressData = append(stressData, types.StressData{
- Date: types.ParseAggregationKey(key, healthAggregate),
- StressLevel: entry.StressLevel / entry.Count,
- RestStressLevel: entry.RestStressLevel / entry.Count,
- })
- }
- }
-
- outputFormat := viper.GetString("output")
-
- switch outputFormat {
- case "json":
- data, err := json.MarshalIndent(stressData, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal stress data to JSON: %w", err)
- }
- fmt.Println(string(data))
- case "csv":
- writer := csv.NewWriter(os.Stdout)
- defer writer.Flush()
-
- writer.Write([]string{"Date", "StressLevel", "RestStressLevel"})
- for _, data := range stressData {
- writer.Write([]string{
- data.Date.Format("2006-01-02"),
- fmt.Sprintf("%d", data.StressLevel),
- fmt.Sprintf("%d", data.RestStressLevel),
- })
- }
- case "table":
- tbl := table.New("Date", "Stress Level", "Rest Stress Level")
- for _, data := range stressData {
- tbl.AddRow(
- data.Date.Format("2006-01-02"),
- fmt.Sprintf("%d", data.StressLevel),
- fmt.Sprintf("%d", data.RestStressLevel),
- )
- }
- tbl.Print()
- default:
- return fmt.Errorf("unsupported output format: %s", outputFormat)
- }
-
- return nil
-}
-
-func runBodyBattery(cmd *cobra.Command, args []string) error {
- garminClient, err := garmin.NewClient(viper.GetString("domain"))
- if err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- }
-
- if err := garminClient.LoadSession(viper.GetString("session_file")); err != nil {
- return fmt.Errorf("not logged in: %w", err)
- }
-
- var targetDate time.Time
- if healthYesterday {
- targetDate = time.Now().AddDate(0, 0, -1)
- } else {
- targetDate = time.Now()
- }
-
- bodyBatteryDataFetcher := &data.BodyBatteryDataWithMethods{}
- result, err := bodyBatteryDataFetcher.Get(targetDate, garminClient.InternalClient())
- if err != nil {
- return fmt.Errorf("failed to get Body Battery data: %w", err)
- }
- bodyBatteryData, ok := result.(*data.BodyBatteryDataWithMethods)
- if !ok {
- return fmt.Errorf("unexpected type for Body Battery data: %T", result)
- }
-
- if bodyBatteryData == nil {
- fmt.Println("No Body Battery data found.")
- return nil
- }
-
- outputFormat := viper.GetString("output.format")
-
- switch outputFormat {
- case "json":
- data, err := json.MarshalIndent(bodyBatteryData, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal Body Battery data to JSON: %w", err)
- }
- fmt.Println(string(data))
- case "csv":
- writer := csv.NewWriter(os.Stdout)
- defer writer.Flush()
-
- writer.Write([]string{"Date", "CurrentLevel", "DayChange", "MaxStressLevel", "AvgStressLevel"})
- writer.Write([]string{
- bodyBatteryData.CalendarDate.Format("2006-01-02"),
- fmt.Sprintf("%d", bodyBatteryData.GetCurrentLevel()),
- fmt.Sprintf("%d", bodyBatteryData.GetDayChange()),
- fmt.Sprintf("%d", bodyBatteryData.MaxStressLevel),
- fmt.Sprintf("%d", bodyBatteryData.AvgStressLevel),
- })
- case "table":
- tbl := table.New("Date", "Current Level", "Day Change", "Max Stress", "Avg Stress")
- tbl.AddRow(
- bodyBatteryData.CalendarDate.Format("2006-01-02"),
- fmt.Sprintf("%d", bodyBatteryData.GetCurrentLevel()),
- fmt.Sprintf("%d", bodyBatteryData.GetDayChange()),
- fmt.Sprintf("%d", bodyBatteryData.MaxStressLevel),
- fmt.Sprintf("%d", bodyBatteryData.AvgStressLevel),
- )
- tbl.Print()
- default:
- return fmt.Errorf("unsupported output format: %s", outputFormat)
- }
-
- return nil
-}
-
-func runVO2Max(cmd *cobra.Command, args []string) error {
- client, err := garmin.NewClient(viper.GetString("domain"))
- if err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- }
-
- if err := client.LoadSession(viper.GetString("session_file")); err != nil {
- return fmt.Errorf("not logged in: %w", err)
- }
-
- profile, err := client.InternalClient().GetCurrentVO2Max()
- if err != nil {
- return fmt.Errorf("failed to get VO2 Max data: %w", err)
- }
-
- if profile.Running == nil && profile.Cycling == nil {
- fmt.Println("No VO2 Max data found.")
- return nil
- }
-
- outputFormat := viper.GetString("output.format")
-
- switch outputFormat {
- case "json":
- data, err := json.MarshalIndent(profile, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal VO2 Max data to JSON: %w", err)
- }
- fmt.Println(string(data))
- case "csv":
- writer := csv.NewWriter(os.Stdout)
- defer writer.Flush()
-
- writer.Write([]string{"Type", "Value", "Date", "Source"})
- if profile.Running != nil {
- writer.Write([]string{
- profile.Running.ActivityType,
- fmt.Sprintf("%.2f", profile.Running.Value),
- profile.Running.Date.Format("2006-01-02"),
- profile.Running.Source,
- })
- }
- if profile.Cycling != nil {
- writer.Write([]string{
- profile.Cycling.ActivityType,
- fmt.Sprintf("%.2f", profile.Cycling.Value),
- profile.Cycling.Date.Format("2006-01-02"),
- profile.Cycling.Source,
- })
- }
- case "table":
- tbl := table.New("Type", "Value", "Date", "Source")
-
- if profile.Running != nil {
- tbl.AddRow(
- profile.Running.ActivityType,
- fmt.Sprintf("%.2f", profile.Running.Value),
- profile.Running.Date.Format("2006-01-02"),
- profile.Running.Source,
- )
- }
- if profile.Cycling != nil {
- tbl.AddRow(
- profile.Cycling.ActivityType,
- fmt.Sprintf("%.2f", profile.Cycling.Value),
- fmt.Sprintf("%.2f", profile.Cycling.Value),
- profile.Cycling.Date.Format("2006-01-02"),
- profile.Cycling.Source,
- )
- }
- tbl.Print()
- default:
- return fmt.Errorf("unsupported output format: %s", outputFormat)
- }
-
- return nil
-}
-
-func runHRZones(cmd *cobra.Command, args []string) error {
- garminClient, err := garmin.NewClient(viper.GetString("domain"))
- if err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- }
-
- if err := garminClient.LoadSession(viper.GetString("session_file")); err != nil {
- return fmt.Errorf("not logged in: %w", err)
- }
-
- hrZonesData, err := garminClient.GetHeartRateZones()
- if err != nil {
- return fmt.Errorf("failed to get Heart Rate Zones data: %w", err)
- }
-
- if hrZonesData == nil {
- fmt.Println("No Heart Rate Zones data found.")
- return nil
- }
-
- outputFormat := viper.GetString("output")
-
- switch outputFormat {
- case "json":
- data, err := json.MarshalIndent(hrZonesData, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal Heart Rate Zones data to JSON: %w", err)
- }
- fmt.Println(string(data))
- case "csv":
- writer := csv.NewWriter(os.Stdout)
- defer writer.Flush()
-
- writer.Write([]string{"Zone", "MinBPM", "MaxBPM", "Name"})
- for _, zone := range hrZonesData.Zones {
- writer.Write([]string{
- strconv.Itoa(zone.Zone),
- strconv.Itoa(zone.MinBPM),
- strconv.Itoa(zone.MaxBPM),
- zone.Name,
- })
- }
- case "table":
- tbl := table.New("Resting HR", "Max HR", "Lactate Threshold", "Updated At")
- tbl.AddRow(
- strconv.Itoa(hrZonesData.RestingHR),
- strconv.Itoa(hrZonesData.MaxHR),
- strconv.Itoa(hrZonesData.LactateThreshold),
- hrZonesData.UpdatedAt.Format("2006-01-02 15:04:05"),
- )
- tbl.Print()
-
- fmt.Println()
-
- zonesTable := table.New("Zone", "Min BPM", "Max BPM", "Name")
- for _, zone := range hrZonesData.Zones {
- zonesTable.AddRow(
- strconv.Itoa(zone.Zone),
- strconv.Itoa(zone.MinBPM),
- strconv.Itoa(zone.MaxBPM),
- zone.Name,
- )
- }
- zonesTable.Print()
- default:
- return fmt.Errorf("unsupported output format: %s", outputFormat)
- }
-
- return nil
-}
-
-func runWellness(cmd *cobra.Command, args []string) error {
- return fmt.Errorf("not implemented")
-}
-
-func runTrainingStatus(cmd *cobra.Command, args []string) error {
- garminClient, err := garmin.NewClient(viper.GetString("domain"))
- if err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- }
-
- if err := garminClient.LoadSession(viper.GetString("session_file")); err != nil {
- return fmt.Errorf("not logged in: %w", err)
- }
-
- var targetDate time.Time
- if healthDateFrom != "" {
- targetDate, err = time.Parse("2006-01-02", healthDateFrom)
- if err != nil {
- return fmt.Errorf("invalid date format for --from: %w", err)
- }
- } else {
- targetDate = time.Now()
- }
-
- trainingStatusFetcher := &data.TrainingStatusWithMethods{}
- trainingStatus, err := trainingStatusFetcher.Get(targetDate, garminClient.InternalClient())
- if err != nil {
- return fmt.Errorf("failed to get training status: %w", err)
- }
-
- if trainingStatus == nil {
- fmt.Println("No training status data found.")
- return nil
- }
-
- tsm, ok := trainingStatus.(*data.TrainingStatusWithMethods)
- if !ok {
- return fmt.Errorf("unexpected type returned for training status: %T", trainingStatus)
- }
-
- outputFormat := viper.GetString("output.format")
-
- switch outputFormat {
- case "json":
- data, err := json.MarshalIndent(tsm, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal training status to JSON: %w", err)
- }
- fmt.Println(string(data))
- case "csv":
- writer := csv.NewWriter(os.Stdout)
- defer writer.Flush()
-
- writer.Write([]string{"Date", "Status", "LoadRatio"})
- writer.Write([]string{
- tsm.CalendarDate.Format("2006-01-02"),
- tsm.TrainingStatusKey,
- fmt.Sprintf("%.2f", tsm.LoadRatio),
- })
- case "table":
- tbl := table.New("Date", "Status", "Load Ratio")
- tbl.AddRow(
- tsm.CalendarDate.Format("2006-01-02"),
- tsm.TrainingStatusKey,
- fmt.Sprintf("%.2f", tsm.LoadRatio),
- )
- tbl.Print()
- default:
- return fmt.Errorf("unsupported output format: %s", outputFormat)
- }
-
- return nil
-}
-
-func runTrainingLoad(cmd *cobra.Command, args []string) error {
- garminClient, err := garmin.NewClient(viper.GetString("domain"))
- if err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- }
-
- if err := garminClient.LoadSession(viper.GetString("session_file")); err != nil {
- return fmt.Errorf("not logged in: %w", err)
- }
-
- var targetDate time.Time
- if healthDateFrom != "" {
- targetDate, err = time.Parse("2006-01-02", healthDateFrom)
- if err != nil {
- return fmt.Errorf("invalid date format for --from: %w", err)
- }
- } else {
- targetDate = time.Now()
- }
-
- trainingLoadFetcher := &data.TrainingLoadWithMethods{}
- trainingLoad, err := trainingLoadFetcher.Get(targetDate, garminClient.InternalClient())
- if err != nil {
- return fmt.Errorf("failed to get training load: %w", err)
- }
-
- if trainingLoad == nil {
- fmt.Println("No training load data found.")
- return nil
- }
-
- tlm, ok := trainingLoad.(*data.TrainingLoadWithMethods)
- if !ok {
- return fmt.Errorf("unexpected type returned for training load: %T", trainingLoad)
- }
-
- outputFormat := viper.GetString("output.format")
-
- switch outputFormat {
- case "json":
- data, err := json.MarshalIndent(tlm, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal training load to JSON: %w", err)
- }
- fmt.Println(string(data))
- case "csv":
- writer := csv.NewWriter(os.Stdout)
- defer writer.Flush()
-
- writer.Write([]string{"Date", "AcuteLoad", "ChronicLoad", "LoadRatio"})
- writer.Write([]string{
- tlm.CalendarDate.Format("2006-01-02"),
- fmt.Sprintf("%.2f", tlm.AcuteTrainingLoad),
- fmt.Sprintf("%.2f", tlm.ChronicTrainingLoad),
- fmt.Sprintf("%.2f", tlm.TrainingLoadRatio),
- })
- case "table":
- tbl := table.New("Date", "Acute Load", "Chronic Load", "Load Ratio")
- tbl.AddRow(
- tlm.CalendarDate.Format("2006-01-02"),
- fmt.Sprintf("%.2f", tlm.AcuteTrainingLoad),
- fmt.Sprintf("%.2f", tlm.ChronicTrainingLoad),
- fmt.Sprintf("%.2f", tlm.TrainingLoadRatio),
- )
- tbl.Print()
- default:
- return fmt.Errorf("unsupported output format: %s", outputFormat)
- }
-
- return nil
-}
-
-func runFitnessAge(cmd *cobra.Command, args []string) error {
- garminClient, err := garmin.NewClient(viper.GetString("domain"))
- if err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- }
-
- if err := garminClient.LoadSession(viper.GetString("session_file")); err != nil {
- return fmt.Errorf("not logged in: %w", err)
- }
-
- fitnessAge, err := garminClient.GetFitnessAge()
- if err != nil {
- return fmt.Errorf("failed to get fitness age: %w", err)
- }
-
- if fitnessAge == nil {
- fmt.Println("No fitness age data found.")
- return nil
- }
-
- outputFormat := viper.GetString("output.format")
-
- switch outputFormat {
- case "json":
- data, err := json.MarshalIndent(fitnessAge, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal fitness age to JSON: %w", err)
- }
- fmt.Println(string(data))
- case "csv":
- writer := csv.NewWriter(os.Stdout)
- defer writer.Flush()
-
- writer.Write([]string{"FitnessAge", "ChronologicalAge", "VO2MaxRunning", "LastUpdated"})
- writer.Write([]string{
- fmt.Sprintf("%d", fitnessAge.FitnessAge),
- fmt.Sprintf("%d", fitnessAge.ChronologicalAge),
- fmt.Sprintf("%.2f", fitnessAge.VO2MaxRunning),
- fitnessAge.LastUpdated.Format("2006-01-02"),
- })
- case "table":
- tbl := table.New("Fitness Age", "Chronological Age", "VO2 Max Running", "Last Updated")
- tbl.AddRow(
- fmt.Sprintf("%d", fitnessAge.FitnessAge),
- fmt.Sprintf("%d", fitnessAge.ChronologicalAge),
- fmt.Sprintf("%.2f", fitnessAge.VO2MaxRunning),
- fitnessAge.LastUpdated.Format("2006-01-02"),
- )
- tbl.Print()
- default:
- return fmt.Errorf("unsupported output format: %s", outputFormat)
- }
-
- return nil
-}
diff --git a/cmd/garth/main.go b/cmd/garth/main.go
deleted file mode 100644
index 736ef31..0000000
--- a/cmd/garth/main.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package main
-
-func main() {
- Execute()
-}
diff --git a/cmd/garth/root.go b/cmd/garth/root.go
deleted file mode 100644
index 446758a..0000000
--- a/cmd/garth/root.go
+++ /dev/null
@@ -1,117 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
- "path/filepath"
-
- "github.com/spf13/cobra"
- "github.com/spf13/viper"
-
- "github.com/sstent/go-garth/internal/config"
-)
-
-var (
- cfgFile string
- userConfigDir string
- cfg *config.Config
-)
-
-// rootCmd represents the base command when called without any subcommands
-var rootCmd = &cobra.Command{
- Use: "garth",
- Short: "Garmin Connect CLI tool",
- Long: `A comprehensive CLI tool for interacting with Garmin Connect.
-
-Garth allows you to fetch your Garmin Connect data, including activities,
-health stats, and more, directly from your terminal.`,
- PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
- // Ensure config is loaded before any command runs
- if cfg == nil {
- return fmt.Errorf("configuration not loaded")
- }
- return nil
- },
-}
-
-// Execute adds all child commands to the root command and sets flags appropriately.
-// This is called by main.main(). It only needs to happen once to the rootCmd.
-func Execute() {
- err := rootCmd.Execute()
- if err != nil {
- os.Exit(1)
- }
-}
-
-func init() {
- cobra.OnInitialize(initConfig)
-
- // Global flags
- rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.config/garth/config.yaml)")
- rootCmd.PersistentFlags().StringVar(&userConfigDir, "config-dir", "", "config directory (default is $HOME/.config/garth)")
-
- rootCmd.PersistentFlags().String("output", "table", "output format (json, table, csv)")
- rootCmd.PersistentFlags().Bool("verbose", false, "enable verbose output")
- rootCmd.PersistentFlags().String("date-from", "", "start date for data fetching (YYYY-MM-DD)")
- rootCmd.PersistentFlags().String("date-to", "", "end date for data fetching (YYYY-MM-DD)")
-
- // Bind flags to viper
- _ = viper.BindPFlag("output.format", rootCmd.PersistentFlags().Lookup("output"))
- _ = viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose"))
- _ = viper.BindPFlag("dateFrom", rootCmd.PersistentFlags().Lookup("date-from"))
- _ = viper.BindPFlag("dateTo", rootCmd.PersistentFlags().Lookup("date-to"))
-}
-
-// initConfig reads in config file and ENV variables if set.
-func initConfig() {
- if userConfigDir == "" {
- userConfigDir = config.UserConfigDir()
- }
-
- if cfgFile != "" {
- // Use config file from the flag.
- viper.SetConfigFile(cfgFile)
- } else {
- // Search config in user's config directory with name "config" (without extension).
- viper.AddConfigPath(userConfigDir)
- viper.SetConfigName("config")
- viper.SetConfigType("yaml")
- }
-
- viper.AutomaticEnv() // read in environment variables that match
-
- // If a config file is found, read it in.
- if err := viper.ReadInConfig(); err == nil {
- fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
- } else {
- // If config file not found, try to initialize a default one
- defaultConfigPath := filepath.Join(userConfigDir, "config.yaml")
- if _, statErr := os.Stat(defaultConfigPath); os.IsNotExist(statErr) {
- fmt.Fprintln(os.Stderr, "No config file found. Initializing default config at:", defaultConfigPath)
- var initErr error
- cfg, initErr = config.InitConfig(defaultConfigPath)
- if initErr != nil {
- fmt.Fprintln(os.Stderr, "Error initializing default config:", initErr)
- os.Exit(1)
- }
- } else if statErr != nil {
- fmt.Fprintln(os.Stderr, "Error checking for config file:", statErr)
- os.Exit(1)
- }
- }
-
- // Unmarshal config into our struct
- if cfg == nil { // Only unmarshal if not already initialized by InitConfig
- cfg = config.DefaultConfig() // Start with defaults
- if err := viper.Unmarshal(cfg); err != nil {
- fmt.Fprintln(os.Stderr, "Error unmarshaling config:", err)
- os.Exit(1)
- }
- }
-
- // Override config with flag values
- if rootCmd.PersistentFlags().Lookup("output").Changed {
- cfg.Output.Format = viper.GetString("output.format")
- }
- // Add other flag overrides as needed
-}
diff --git a/cmd/garth/stats.go b/cmd/garth/stats.go
deleted file mode 100644
index 32685e6..0000000
--- a/cmd/garth/stats.go
+++ /dev/null
@@ -1,238 +0,0 @@
-package main
-
-import (
- "encoding/csv"
- "encoding/json"
- "fmt"
- "os"
- "time"
-
- "github.com/rodaine/table"
- "github.com/spf13/cobra"
- "github.com/spf13/viper"
-
- types "github.com/sstent/go-garth/internal/models/types"
- "github.com/sstent/go-garth/pkg/garmin"
-)
-
-var (
- statsYear bool
- statsAggregate string
- statsFrom string
-)
-
-func runDistance(cmd *cobra.Command, args []string) error {
- garminClient, err := garmin.NewClient("www.garmin.com") // TODO: Domain should be configurable
- if err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- }
-
- sessionFile := "garmin_session.json" // TODO: Make session file configurable
- if err := garminClient.LoadSession(sessionFile); err != nil {
- return fmt.Errorf("not logged in: %w", err)
- }
-
- var startDate, endDate time.Time
- if statsYear {
- now := time.Now()
- startDate = time.Date(now.Year(), time.January, 1, 0, 0, 0, 0, now.Location())
- endDate = time.Date(now.Year(), time.December, 31, 0, 0, 0, 0, now.Location()) // Last day of the year
- } else {
- // Default to today if no specific range or year is given
- startDate = time.Now()
- endDate = time.Now()
- }
-
- distanceData, err := garminClient.GetDistanceData(startDate, endDate)
- if err != nil {
- return fmt.Errorf("failed to get distance data: %w", err)
- }
-
- if len(distanceData) == 0 {
- fmt.Println("No distance data found.")
- return nil
- }
-
- // Apply aggregation if requested
- if statsAggregate != "" {
- aggregatedDistance := make(map[string]struct {
- Distance float64
- Count int
- })
-
- for _, data := range distanceData {
- key := ""
- switch statsAggregate {
- case "day":
- key = data.Date.Format("2006-01-02")
- case "week":
- year, week := data.Date.ISOWeek()
- key = fmt.Sprintf("%d-W%02d", year, week)
- case "month":
- key = data.Date.Format("2006-01")
- case "year":
- key = data.Date.Format("2006")
- default:
- return fmt.Errorf("unsupported aggregation period: %s", statsAggregate)
- }
-
- entry := aggregatedDistance[key]
- entry.Distance += data.Distance
- entry.Count++
- aggregatedDistance[key] = entry
- }
-
- // Convert aggregated data back to a slice for output
- distanceData = []types.DistanceData{}
- for key, entry := range aggregatedDistance {
- distanceData = append(distanceData, types.DistanceData{
- Date: types.ParseAggregationKey(key, statsAggregate), // Helper to parse key back to date
- Distance: entry.Distance / float64(entry.Count),
- })
- }
- }
-
- outputFormat := viper.GetString("output")
-
- switch outputFormat {
- case "json":
- data, err := json.MarshalIndent(distanceData, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal distance data to JSON: %w", err)
- }
- fmt.Println(string(data))
- case "csv":
- writer := csv.NewWriter(os.Stdout)
- defer writer.Flush()
-
- writer.Write([]string{"Date", "Distance(km)"})
- for _, data := range distanceData {
- writer.Write([]string{
- data.Date.Format("2006-01-02"),
- fmt.Sprintf("%.2f", data.Distance/1000),
- })
- }
- case "table":
- tbl := table.New("Date", "Distance (km)")
- for _, data := range distanceData {
- tbl.AddRow(
- data.Date.Format("2006-01-02"),
- fmt.Sprintf("%.2f", data.Distance/1000),
- )
- }
- tbl.Print()
- default:
- return fmt.Errorf("unsupported output format: %s", outputFormat)
- }
-
- return nil
-}
-
-func runCalories(cmd *cobra.Command, args []string) error {
- garminClient, err := garmin.NewClient("www.garmin.com") // TODO: Domain should be configurable
- if err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- }
-
- sessionFile := "garmin_session.json" // TODO: Make session file configurable
- if err := garminClient.LoadSession(sessionFile); err != nil {
- return fmt.Errorf("not logged in: %w", err)
- }
-
- var startDate, endDate time.Time
- if statsFrom != "" {
- startDate, err = time.Parse("2006-01-02", statsFrom)
- if err != nil {
- return fmt.Errorf("invalid date format for --from: %w", err)
- }
- endDate = time.Now() // Default end date to today if only from is provided
- } else {
- // Default to today if no specific range is given
- startDate = time.Now()
- endDate = time.Now()
- }
-
- caloriesData, err := garminClient.GetCaloriesData(startDate, endDate)
- if err != nil {
- return fmt.Errorf("failed to get calories data: %w", err)
- }
-
- if len(caloriesData) == 0 {
- fmt.Println("No calories data found.")
- return nil
- }
-
- // Apply aggregation if requested
- if statsAggregate != "" {
- aggregatedCalories := make(map[string]struct {
- Calories int
- Count int
- })
-
- for _, data := range caloriesData {
- key := ""
- switch statsAggregate {
- case "day":
- key = data.Date.Format("2006-01-02")
- case "week":
- year, week := data.Date.ISOWeek()
- key = fmt.Sprintf("%d-W%02d", year, week)
- case "month":
- key = data.Date.Format("2006-01")
- case "year":
- key = data.Date.Format("2006")
- default:
- return fmt.Errorf("unsupported aggregation period: %s", statsAggregate)
- }
-
- entry := aggregatedCalories[key]
- entry.Calories += data.Calories
- entry.Count++
- aggregatedCalories[key] = entry
- }
-
- // Convert aggregated data back to a slice for output
- caloriesData = []types.CaloriesData{}
- for key, entry := range aggregatedCalories {
- caloriesData = append(caloriesData, types.CaloriesData{
- Date: types.ParseAggregationKey(key, statsAggregate), // Helper to parse key back to date
- Calories: entry.Calories / entry.Count,
- })
- }
- }
-
- outputFormat := viper.GetString("output")
-
- switch outputFormat {
- case "json":
- data, err := json.MarshalIndent(caloriesData, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal calories data to JSON: %w", err)
- }
- fmt.Println(string(data))
- case "csv":
- writer := csv.NewWriter(os.Stdout)
- defer writer.Flush()
-
- writer.Write([]string{"Date", "Calories"})
- for _, data := range caloriesData {
- writer.Write([]string{
- data.Date.Format("2006-01-02"),
- fmt.Sprintf("%d", data.Calories),
- })
- }
- case "table":
- tbl := table.New("Date", "Calories")
- for _, data := range caloriesData {
- tbl.AddRow(
- data.Date.Format("2006-01-02"),
- fmt.Sprintf("%d", data.Calories),
- )
- }
- tbl.Print()
- default:
- return fmt.Errorf("unsupported output format: %s", outputFormat)
- }
-
- return nil
-}
diff --git a/e2e_test.sh b/e2e_test.sh
deleted file mode 100755
index eb21ed6..0000000
--- a/e2e_test.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-
-set -e
-
-echo "--- Running End-to-End CLI Tests ---"
-
-echo "Testing garth --help"
-go run go-garth/cmd/garth --help
-
-echo "Testing garth auth status"
-go run go-garth/cmd/garth auth status
-
-echo "Testing garth activities list"
-go run go-garth/cmd/garth activities list --limit 5
-
-echo "Testing garth health sleep"
-go run go-garth/cmd/garth health sleep --from 2024-01-01 --to 2024-01-02
-
-echo "Testing garth stats distance"
-go run go-garth/cmd/garth stats distance --year
-
-echo "Testing garth health vo2max"
-go run go-garth/cmd/garth health vo2max --from 2024-01-01 --to 2024-01-02
-
-echo "Testing garth health hr-zones"
-go run go-garth/cmd/garth health hr-zones
-
-echo "--- End-to-End CLI Tests Passed ---"
\ No newline at end of file
diff --git a/garth b/garth
deleted file mode 100755
index 069a613..0000000
Binary files a/garth and /dev/null differ
diff --git a/go.mod b/go.mod
index a62f011..c5ff17b 100644
--- a/go.mod
+++ b/go.mod
@@ -2,31 +2,11 @@ module github.com/sstent/go-garth
go 1.24.2
-require (
- github.com/joho/godotenv v1.5.1
- github.com/rodaine/table v1.3.0
- github.com/schollz/progressbar/v3 v3.18.0
- github.com/spf13/cobra v1.10.1
- github.com/spf13/viper v1.21.0
- golang.org/x/term v0.28.0
-)
+require github.com/joho/godotenv v1.5.1
require (
- github.com/fsnotify/fsnotify v1.9.0 // indirect
- github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
- github.com/inconshreveable/mousetrap v1.1.0 // indirect
- github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
- github.com/pelletier/go-toml/v2 v2.2.4 // indirect
- github.com/rivo/uniseg v0.4.7 // indirect
- github.com/sagikazarmark/locafero v0.11.0 // indirect
- github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
- github.com/spf13/afero v1.15.0 // indirect
- github.com/spf13/cast v1.10.0 // indirect
- github.com/spf13/pflag v1.0.10 // indirect
- github.com/subosito/gotenv v1.6.0 // indirect
- go.yaml.in/yaml/v3 v3.0.4 // indirect
- golang.org/x/sys v0.36.0 // indirect
- golang.org/x/text v0.28.0 // indirect
+ github.com/kr/pretty v0.3.1 // indirect
+ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
)
require (
diff --git a/go.sum b/go.sum
index ae478a6..3e7493f 100644
--- a/go.sum
+++ b/go.sum
@@ -1,81 +1,21 @@
-github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
-github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
-github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
-github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
-github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
-github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
-github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
-github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
-github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
-github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
-github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
-github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
-github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
-github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
-github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
-github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
-github.com/rodaine/table v1.3.0 h1:4/3S3SVkHnVZX91EHFvAMV7K42AnJ0XuymRR2C5HlGE=
-github.com/rodaine/table v1.3.0/go.mod h1:47zRsHar4zw0jgxGxL9YtFfs7EGN6B/TaS+/Dmk4WxU=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
-github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
-github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
-github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
-github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
-github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
-github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
-github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
-github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
-github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
-github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
-github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
-github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
-github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
-github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
-github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
-github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
-github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
-go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
-go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
-golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
-golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
-golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
-golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
-golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
-golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/pkg/auth/oauth/oauth.go b/pkg/auth/oauth/oauth.go
new file mode 100644
index 0000000..f35f99c
--- /dev/null
+++ b/pkg/auth/oauth/oauth.go
@@ -0,0 +1,25 @@
+package oauth
+
+import (
+ "github.com/sstent/go-garth/internal/auth/oauth"
+ "github.com/sstent/go-garth/internal/models/types"
+ "github.com/sstent/go-garth/pkg/garmin"
+)
+
+// GetOAuth1Token retrieves an OAuth1 token using the provided ticket
+func GetOAuth1Token(domain, ticket string) (*garmin.OAuth1Token, error) {
+ token, err := oauth.GetOAuth1Token(domain, ticket)
+ if err != nil {
+ return nil, err
+ }
+ return (*garmin.OAuth1Token)(token), nil
+}
+
+// ExchangeToken exchanges an OAuth1 token for an OAuth2 token
+func ExchangeToken(oauth1Token *garmin.OAuth1Token) (*garmin.OAuth2Token, error) {
+ token, err := oauth.ExchangeToken((*types.OAuth1Token)(oauth1Token))
+ if err != nil {
+ return nil, err
+ }
+ return (*garmin.OAuth2Token)(token), nil
+}
diff --git a/python-garmin-connect/Activity.go b/python-garmin-connect/Activity.go
deleted file mode 100644
index 7f4945a..0000000
--- a/python-garmin-connect/Activity.go
+++ /dev/null
@@ -1,229 +0,0 @@
-package connect
-
-import (
- "archive/zip"
- "bytes"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "mime/multipart"
- "net/http"
- "strings"
-)
-
-// Activity describes a Garmin Connect activity.
-type Activity struct {
- ID int `json:"activityId"`
- ActivityName string `json:"activityName"`
- Description string `json:"description"`
- StartLocal Time `json:"startTimeLocal"`
- StartGMT Time `json:"startTimeGMT"`
- ActivityType ActivityType `json:"activityType"`
- Distance float64 `json:"distance"` // meter
- Duration float64 `json:"duration"`
- ElapsedDuration float64 `json:"elapsedDuration"`
- MovingDuration float64 `json:"movingDuration"`
- AverageSpeed float64 `json:"averageSpeed"`
- MaxSpeed float64 `json:"maxSpeed"`
- OwnerID int `json:"ownerId"`
- Calories float64 `json:"calories"`
- AverageHeartRate float64 `json:"averageHR"`
- MaxHeartRate float64 `json:"maxHR"`
- DeviceID int `json:"deviceId"`
-}
-
-// ActivityType describes the type of activity.
-type ActivityType struct {
- TypeID int `json:"typeId"`
- TypeKey string `json:"typeKey"`
- ParentTypeID int `json:"parentTypeId"`
- SortOrder int `json:"sortOrder"`
-}
-
-// Activity will retrieve details about an activity.
-func (c *Client) Activity(activityID int) (*Activity, error) {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/activity-service/activity/%d",
- activityID,
- )
-
- activity := new(Activity)
-
- err := c.getJSON(URL, &activity)
- if err != nil {
- return nil, err
- }
-
- return activity, nil
-}
-
-// Activities will list activities for displayName. If displayName is empty,
-// the authenticated user will be used.
-func (c *Client) Activities(displayName string, start int, limit int) ([]Activity, error) {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/activitylist-service/activities/%s?start=%d&limit=%d", displayName, start, limit)
-
- if !c.authenticated() && displayName == "" {
- return nil, ErrNotAuthenticated
- }
-
- var proxy struct {
- List []Activity `json:"activityList"`
- }
-
- err := c.getJSON(URL, &proxy)
- if err != nil {
- return nil, err
- }
-
- return proxy.List, nil
-}
-
-// RenameActivity can be used to rename an activity.
-func (c *Client) RenameActivity(activityID int, newName string) error {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/activity-service/activity/%d", activityID)
-
- payload := struct {
- ID int `json:"activityId"`
- Name string `json:"activityName"`
- }{activityID, newName}
-
- return c.write("PUT", URL, payload, 204)
-}
-
-// ExportActivity will export an activity from Connect. The activity will be written til w.
-func (c *Client) ExportActivity(id int, w io.Writer, format ActivityFormat) error {
- formatTable := [activityFormatMax]string{
- "https://connect.garmin.com/modern/proxy/download-service/files/activity/%d",
- "https://connect.garmin.com/modern/proxy/download-service/export/tcx/activity/%d",
- "https://connect.garmin.com/modern/proxy/download-service/export/gpx/activity/%d",
- "https://connect.garmin.com/modern/proxy/download-service/export/kml/activity/%d",
- "https://connect.garmin.com/modern/proxy/download-service/export/csv/activity/%d",
- }
-
- if format >= activityFormatMax || format < ActivityFormatFIT {
- return errors.New("invalid format")
- }
-
- URL := fmt.Sprintf(formatTable[format], id)
-
- // To unzip FIT files on-the-fly, we treat them specially.
- if format == ActivityFormatFIT {
- buffer := bytes.NewBuffer(nil)
-
- err := c.Download(URL, buffer)
- if err != nil {
- return err
- }
-
- z, err := zip.NewReader(bytes.NewReader(buffer.Bytes()), int64(buffer.Len()))
- if err != nil {
- return err
- }
-
- if len(z.File) != 1 {
- return fmt.Errorf("%d files found in FIT archive, 1 expected", len(z.File))
- }
-
- src, err := z.File[0].Open()
- if err != nil {
- return err
- }
- defer src.Close()
-
- _, err = io.Copy(w, src)
- return err
- }
-
- return c.Download(URL, w)
-}
-
-// ImportActivity will import an activity into Garmin Connect. The activity
-// will be read from file.
-func (c *Client) ImportActivity(file io.Reader, format ActivityFormat) (int, error) {
- URL := "https://connect.garmin.com/modern/proxy/upload-service/upload/." + format.Extension()
-
- switch format {
- case ActivityFormatFIT, ActivityFormatTCX, ActivityFormatGPX:
- // These are ok.
- default:
- return 0, fmt.Errorf("%s is not supported for import", format.Extension())
- }
-
- formData := bytes.Buffer{}
- writer := multipart.NewWriter(&formData)
- defer writer.Close()
-
- activity, err := writer.CreateFormFile("file", "activity."+format.Extension())
- if err != nil {
- return 0, err
- }
-
- _, err = io.Copy(activity, file)
- if err != nil {
- return 0, err
- }
-
- writer.Close()
-
- req, err := c.newRequest("POST", URL, &formData)
- if err != nil {
- return 0, err
- }
-
- req.Header.Add("content-type", writer.FormDataContentType())
-
- resp, err := c.do(req)
- if err != nil {
- return 0, err
- }
- defer resp.Body.Close()
-
- // Implement enough of the response to satisfy our needs.
- var response struct {
- ImportResult struct {
- Successes []struct {
- InternalID int `json:"internalId"`
- } `json:"successes"`
-
- Failures []struct {
- Messages []struct {
- Content string `json:"content"`
- } `json:"messages"`
- } `json:"failures"`
- } `json:"detailedImportResult"`
- }
-
- err = json.NewDecoder(resp.Body).Decode(&response)
- if err != nil {
- return 0, err
- }
-
- // This is ugly.
- if len(response.ImportResult.Failures) > 0 {
- messages := make([]string, 0, 10)
- for _, f := range response.ImportResult.Failures {
- for _, m := range f.Messages {
- messages = append(messages, m.Content)
- }
- }
-
- return 0, errors.New(strings.Join(messages, "; "))
- }
-
- if resp.StatusCode != 201 {
- return 0, fmt.Errorf("%d: %s", resp.StatusCode, http.StatusText(resp.StatusCode))
- }
-
- if len(response.ImportResult.Successes) != 1 {
- return 0, Error("cannot parse response, no failures and no successes..?")
- }
-
- return response.ImportResult.Successes[0].InternalID, nil
-}
-
-// DeleteActivity will permanently delete an activity.
-func (c *Client) DeleteActivity(id int) error {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/activity-service/activity/%d", id)
-
- return c.write("DELETE", URL, nil, 0)
-}
diff --git a/python-garmin-connect/ActivityFormat.go b/python-garmin-connect/ActivityFormat.go
deleted file mode 100644
index f4b0af9..0000000
--- a/python-garmin-connect/ActivityFormat.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package connect
-
-import (
- "path/filepath"
- "strings"
-)
-
-// ActivityFormat is a file format for importing and exporting activities.
-type ActivityFormat int
-
-const (
- // ActivityFormatFIT is the "original" Garmin format.
- ActivityFormatFIT ActivityFormat = iota
-
- // ActivityFormatTCX is Training Center XML (TCX) format.
- ActivityFormatTCX
-
- // ActivityFormatGPX will export as GPX - the GPS Exchange Format.
- ActivityFormatGPX
-
- // ActivityFormatKML will export KML files compatible with Google Earth.
- ActivityFormatKML
-
- // ActivityFormatCSV will export splits as CSV.
- ActivityFormatCSV
-
- activityFormatMax
- activityFormatInvalid
-)
-
-const (
- // ErrUnknownFormat will be returned if the activity file format is unknown.
- ErrUnknownFormat = Error("Unknown format")
-)
-
-var (
- activityFormatTable = map[string]ActivityFormat{
- "fit": ActivityFormatFIT,
- "tcx": ActivityFormatTCX,
- "gpx": ActivityFormatGPX,
- "kml": ActivityFormatKML,
- "csv": ActivityFormatCSV,
- }
-)
-
-// Extension returns an appropriate filename extension for format.
-func (f ActivityFormat) Extension() string {
- for extension, format := range activityFormatTable {
- if format == f {
- return extension
- }
- }
-
- return ""
-}
-
-// FormatFromExtension tries to guess the format from a file extension.
-func FormatFromExtension(extension string) (ActivityFormat, error) {
- extension = strings.ToLower(extension)
-
- format, found := activityFormatTable[extension]
- if !found {
- return activityFormatInvalid, ErrUnknownFormat
- }
-
- return format, nil
-}
-
-// FormatFromFilename tries to guess the format based on a filename (or path).
-func FormatFromFilename(filename string) (ActivityFormat, error) {
- extension := filepath.Ext(filename)
- extension = strings.TrimPrefix(extension, ".")
-
- return FormatFromExtension(extension)
-}
diff --git a/python-garmin-connect/ActivityHrZones.go b/python-garmin-connect/ActivityHrZones.go
deleted file mode 100644
index a7246c1..0000000
--- a/python-garmin-connect/ActivityHrZones.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package connect
-
-import (
- "fmt"
- "time"
-)
-
-// ActivityHrZones describes the heart-rate zones during an activity.
-type ActivityHrZones struct {
- TimeInZone time.Duration `json:"secsInZone"`
- ZoneLowBoundary int `json:"zoneLowBoundary"`
- ZoneNumber int `json:"zoneNumber"`
-}
-
-// ActivityHrZones returns the reported heart-rate zones for an activity.
-func (c *Client) ActivityHrZones(activityID int) ([]ActivityHrZones, error) {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/activity-service/activity/%d/hrTimeInZones",
- activityID,
- )
-
- var proxy []struct {
- TimeInZone float64 `json:"secsInZone"`
- ZoneLowBoundary int `json:"zoneLowBoundary"`
- ZoneNumber int `json:"zoneNumber"`
- }
-
- err := c.getJSON(URL, &proxy)
- if err != nil {
- return nil, err
- }
-
- zones := make([]ActivityHrZones, len(proxy))
-
- for i, p := range proxy {
- zones[i].TimeInZone = time.Duration(p.TimeInZone * float64(time.Second))
- zones[i].ZoneLowBoundary = p.ZoneLowBoundary
- zones[i].ZoneNumber = p.ZoneNumber
- }
-
- return zones, nil
-}
diff --git a/python-garmin-connect/ActivityWeather.go b/python-garmin-connect/ActivityWeather.go
deleted file mode 100644
index 9fca035..0000000
--- a/python-garmin-connect/ActivityWeather.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package connect
-
-import (
- "fmt"
-)
-
-// ActivityWeather describes the weather during an activity.
-type ActivityWeather struct {
- Temperature int `json:"temp"`
- ApparentTemperature int `json:"apparentTemp"`
- DewPoint int `json:"dewPoint"`
- RelativeHumidity int `json:"relativeHumidity"`
- WindDirection int `json:"windDirection"`
- WindDirectionCompassPoint string `json:"windDirectionCompassPoint"`
- WindSpeed int `json:"windSpeed"`
- Latitude float64 `json:"latitude"`
- Longitude float64 `json:"longitude"`
-}
-
-// ActivityWeather returns the reported weather for an activity.
-func (c *Client) ActivityWeather(activityID int) (*ActivityWeather, error) {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/weather-service/weather/%d",
- activityID,
- )
-
- weather := new(ActivityWeather)
-
- err := c.getJSON(URL, weather)
- if err != nil {
- return nil, err
- }
-
- return weather, nil
-}
diff --git a/python-garmin-connect/AdhocChallenge.go b/python-garmin-connect/AdhocChallenge.go
deleted file mode 100644
index 2e57b2a..0000000
--- a/python-garmin-connect/AdhocChallenge.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package connect
-
-import (
- "fmt"
-)
-
-// Player represents a participant in a challenge.
-type Player struct {
- UserProfileID int `json:"userProfileId"`
- TotalNumber float64 `json:"totalNumber"`
- LastSyncTime Time `json:"lastSyncTime"`
- Ranking int `json:"ranking"`
- ProfileImageURLSmall string `json:"profileImageSmall"`
- ProfileImageURLMedium string `json:"profileImageMedium"`
- FullName string `json:"fullName"`
- DisplayName string `json:"displayName"`
- ProUser bool `json:"isProUser"`
- TodayNumber float64 `json:"todayNumber"`
- AcceptedChallenge bool `json:"isAcceptedChallenge"`
-}
-
-// AdhocChallenge is a user-initiated challenge between 2 or more participants.
-type AdhocChallenge struct {
- SocialChallengeStatusID int `json:"socialChallengeStatusId"`
- SocialChallengeActivityTypeID int `json:"socialChallengeActivityTypeId"`
- SocialChallengeType int `json:"socialChallengeType"`
- Name string `json:"adHocChallengeName"`
- Description string `json:"adHocChallengeDesc"`
- OwnerProfileID int `json:"ownerUserProfileId"`
- UUID string `json:"uuid"`
- Start Time `json:"startDate"`
- End Time `json:"endDate"`
- DurationTypeID int `json:"durationTypeId"`
- UserRanking int `json:"userRanking"`
- Players []Player `json:"players"`
-}
-
-// AdhocChallenges will list the currently non-completed Ad-Hoc challenges.
-// Please note that Players will not be populated, use AdhocChallenge() to
-// retrieve players for a challenge.
-func (c *Client) AdhocChallenges() ([]AdhocChallenge, error) {
- URL := "https://connect.garmin.com/modern/proxy/adhocchallenge-service/adHocChallenge/nonCompleted"
-
- if !c.authenticated() {
- return nil, ErrNotAuthenticated
- }
-
- challenges := make([]AdhocChallenge, 0, 10)
-
- err := c.getJSON(URL, &challenges)
- if err != nil {
- return nil, err
- }
-
- return challenges, nil
-}
-
-// HistoricalAdhocChallenges will retrieve the list of completed ad-hoc
-// challenges.
-func (c *Client) HistoricalAdhocChallenges() ([]AdhocChallenge, error) {
- URL := "https://connect.garmin.com/modern/proxy/adhocchallenge-service/adHocChallenge/historical"
-
- if !c.authenticated() {
- return nil, ErrNotAuthenticated
- }
-
- challenges := make([]AdhocChallenge, 0, 100)
-
- err := c.getJSON(URL, &challenges)
- if err != nil {
- return nil, err
- }
-
- return challenges, nil
-}
-
-// AdhocChallenge will retrieve details for challenge with uuid.
-func (c *Client) AdhocChallenge(uuid string) (*AdhocChallenge, error) {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/adhocchallenge-service/adHocChallenge/%s", uuid)
-
- challenge := new(AdhocChallenge)
-
- err := c.getJSON(URL, challenge)
- if err != nil {
- return nil, err
- }
-
- return challenge, nil
-}
-
-// LeaveAdhocChallenge will leave an ad-hoc challenge. If profileID is 0, the
-// currently authenticated user will be used.
-func (c *Client) LeaveAdhocChallenge(challengeUUID string, profileID int64) error {
- if profileID == 0 && c.Profile == nil {
- return ErrNotAuthenticated
- }
-
- if profileID == 0 && c.Profile != nil {
- profileID = c.Profile.ProfileID
- }
-
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/adhocchallenge-service/adHocChallenge/%s/player/%d",
- challengeUUID,
- profileID,
- )
-
- return c.write("DELETE", URL, nil, 0)
-}
diff --git a/python-garmin-connect/AdhocChallengeInvitation.go b/python-garmin-connect/AdhocChallengeInvitation.go
deleted file mode 100644
index 2805996..0000000
--- a/python-garmin-connect/AdhocChallengeInvitation.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package connect
-
-import (
- "fmt"
-)
-
-// AdhocChallengeInvitation is a ad-hoc challenge invitation.
-type AdhocChallengeInvitation struct {
- AdhocChallenge `json:",inline"`
-
- UUID string `json:"adHocChallengeUuid"`
- InviteID int `json:"adHocChallengeInviteId"`
- InvitorName string `json:"invitorName"`
- InvitorID int `json:"invitorId"`
- InvitorDisplayName string `json:"invitorDisplayName"`
- InviteeID int `json:"inviteeId"`
- UserImageURL string `json:"userImageUrl"`
-}
-
-// AdhocChallengeInvites list Ad-Hoc challenges awaiting response.
-func (c *Client) AdhocChallengeInvites() ([]AdhocChallengeInvitation, error) {
- URL := "https://connect.garmin.com/modern/proxy/adhocchallenge-service/adHocChallenge/invite"
-
- if !c.authenticated() {
- return nil, ErrNotAuthenticated
- }
-
- challenges := make([]AdhocChallengeInvitation, 0, 10)
-
- err := c.getJSON(URL, &challenges)
- if err != nil {
- return nil, err
- }
-
- // Make sure the embedded UUID matches in case the user uses the embedded
- // AdhocChallenge for something.
- for i := range challenges {
- challenges[i].AdhocChallenge.UUID = challenges[i].UUID
- }
-
- return challenges, nil
-}
-
-// AdhocChallengeInvitationRespond will respond to a ad-hoc challenge. If
-// accept is false, the challenge will be declined.
-func (c *Client) AdhocChallengeInvitationRespond(inviteID int, accept bool) error {
- scope := "decline"
- if accept {
- scope = "accept"
- }
-
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/adhocchallenge-service/adHocChallenge/invite/%d/%s", inviteID, scope)
-
- payload := struct {
- InviteID int `json:"inviteId"`
- Scope string `json:"scope"`
- }{
- inviteID,
- scope,
- }
-
- return c.write("PUT", URL, payload, 0)
-}
diff --git a/python-garmin-connect/Badge.go b/python-garmin-connect/Badge.go
deleted file mode 100644
index f87ac76..0000000
--- a/python-garmin-connect/Badge.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package connect
-
-import (
- "fmt"
-)
-
-// Badge describes a badge.
-type Badge struct {
- ID int `json:"badgeId"`
- Key string `json:"badgeKey"`
- Name string `json:"badgeName"`
- CategoryID int `json:"badgeCategoryId"`
- DifficultyID int `json:"badgeDifficultyId"`
- Points int `json:"badgePoints"`
- TypeID []int `json:"badgeTypeIds"`
- SeriesID int `json:"badgeSeriesId"`
- Start Time `json:"badgeStartDate"`
- End Time `json:"badgeEndDate"`
- UserProfileID int `json:"userProfileId"`
- FullName string `json:"fullName"`
- DisplayName string `json:"displayName"`
- EarnedDate Time `json:"badgeEarnedDate"`
- EarnedNumber int `json:"badgeEarnedNumber"`
- Viewed bool `json:"badgeIsViewed"`
- Progress float64 `json:"badgeProgressValue"`
- Target float64 `json:"badgeTargetValue"`
- UnitID int `json:"badgeUnitId"`
- BadgeAssocTypeID int `json:"badgeAssocTypeId"`
- BadgeAssocDataID string `json:"badgeAssocDataId"`
- BadgeAssocDataName string `json:"badgeAssocDataName"`
- EarnedByMe bool `json:"earnedByMe"`
- RelatedBadges []Badge `json:"relatedBadges"`
- Connections []Badge `json:"connections"`
-}
-
-// BadgeDetail will return details about a badge.
-func (c *Client) BadgeDetail(badgeID int) (*Badge, error) {
- // Alternative URL:
- // https://connect.garmin.com/modern/proxy/badge-service/badge/DISPLAYNAME/earned/detail/BADGEID
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/badge-service/badge/detail/v2/%d",
- badgeID)
-
- badge := new(Badge)
-
- err := c.getJSON(URL, badge)
-
- // This is interesting. Garmin returns 400 if an unknown badge is
- // requested. We have no way of detecting that, so we silently changes
- // the error to ErrNotFound.
- if err == ErrBadRequest {
- return nil, ErrNotFound
- }
-
- if err != nil {
- return nil, err
- }
-
- return badge, nil
-}
diff --git a/python-garmin-connect/BadgeAttributes.go b/python-garmin-connect/BadgeAttributes.go
deleted file mode 100644
index 42fc9be..0000000
--- a/python-garmin-connect/BadgeAttributes.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package connect
-
-// Everything from https://connect.garmin.com/modern/proxy/badge-service/badge/attributes
-
-type BadgeType struct {
- ID int `json:"badgeTypeId"`
- Key string `json:"badgeTypeKey"`
-}
-
-type BadgeCategory struct {
- ID int `json:"badgeCategoryId"`
- Key string `json:"badgeCategoryKey"`
-}
-
-type BadgeDifficulty struct {
- ID int `json:"badgeDifficultyId"`
- Key string `json:"badgeDifficultyKey"`
- Points int `json:"badgePoints"`
-}
-
-type BadgeUnit struct {
- ID int `json:"badgeUnitId"`
- Key string `json:"badgeUnitKey"`
-}
-
-type BadgeAssocType struct {
- ID int `json:"badgeAssocTypeId"`
- Key string `json:"badgeAssocTypeKey"`
-}
-
-type BadgeAttributes struct {
- BadgeTypes []BadgeType `json:"badgeTypes"`
- BadgeCategories []BadgeCategory `json:"badgeCategories"`
- BadgeDifficulties []BadgeDifficulty `json:"badgeDifficulties"`
- BadgeUnits []BadgeUnit `json:"badgeUnits"`
- BadgeAssocTypes []BadgeAssocType `json:"badgeAssocTypes"`
-}
-
-// BadgeAttributes retrieves a list of badge attributes. At time of writing
-// we're not sure how these can be utilized.
-func (c *Client) BadgeAttributes() (*BadgeAttributes, error) {
- URL := "https://connect.garmin.com/modern/proxy/badge-service/badge/attributes"
-
- attributes := new(BadgeAttributes)
-
- err := c.getJSON(URL, &attributes)
- if err != nil {
- return nil, err
- }
-
- return attributes, nil
-}
diff --git a/python-garmin-connect/BadgeStatus.go b/python-garmin-connect/BadgeStatus.go
deleted file mode 100644
index 0a801fe..0000000
--- a/python-garmin-connect/BadgeStatus.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package connect
-
-// BadgeStatus is the badge status for a Connect user.
-type BadgeStatus struct {
- ProfileID int `json:"userProfileId"`
- Fullname string `json:"fullName"`
- DisplayName string `json:"displayName"`
- ProUser bool `json:"userPro"`
- ProfileImageURLLarge string `json:"profileImageUrlLarge"`
- ProfileImageURLMedium string `json:"profileImageUrlMedium"`
- ProfileImageURLSmall string `json:"profileImageUrlSmall"`
- Level int `json:"userLevel"`
- LevelUpdateTime Time `json:"levelUpdateDate"`
- Point int `json:"userPoint"`
- Badges []Badge `json:"badges"`
-}
-
-// BadgeLeaderBoard returns the leaderboard for points for the currently
-// authenticated user.
-func (c *Client) BadgeLeaderBoard() ([]BadgeStatus, error) {
- URL := "https://connect.garmin.com/modern/proxy/badge-service/badge/leaderboard"
-
- if !c.authenticated() {
- return nil, ErrNotAuthenticated
- }
-
- var proxy struct {
- LeaderBoad []BadgeStatus `json:"connections"`
- }
-
- err := c.getJSON(URL, &proxy)
- if err != nil {
- return nil, err
- }
-
- return proxy.LeaderBoad, nil
-}
-
-// BadgeCompare will compare the earned badges of the currently authenticated user against displayName.
-func (c *Client) BadgeCompare(displayName string) (*BadgeStatus, *BadgeStatus, error) {
- URL := "https://connect.garmin.com/modern/proxy/badge-service/badge/compare/" + displayName
-
- if !c.authenticated() {
- return nil, nil, ErrNotAuthenticated
- }
-
- var proxy struct {
- User *BadgeStatus `json:"user"`
- Connection *BadgeStatus `json:"connection"`
- }
-
- err := c.getJSON(URL, &proxy)
- if err != nil {
- return nil, nil, err
- }
-
- return proxy.User, proxy.Connection, nil
-}
-
-// BadgesEarned will return the list of badges earned by the curently
-// authenticated user.
-func (c *Client) BadgesEarned() ([]Badge, error) {
- URL := "https://connect.garmin.com/modern/proxy/badge-service/badge/earned"
-
- if !c.authenticated() {
- return nil, ErrNotAuthenticated
- }
-
- badges := make([]Badge, 0, 200)
- err := c.getJSON(URL, &badges)
- if err != nil {
- return nil, err
- }
-
- return badges, nil
-}
-
-// BadgesAvailable will return the list of badges not yet earned by the curently
-// authenticated user.
-func (c *Client) BadgesAvailable() ([]Badge, error) {
- URL := "https://connect.garmin.com/modern/proxy/badge-service/badge/available"
-
- if !c.authenticated() {
- return nil, ErrNotAuthenticated
- }
-
- badges := make([]Badge, 0, 200)
- err := c.getJSON(URL, &badges)
- if err != nil {
- return nil, err
- }
-
- return badges, nil
-}
diff --git a/python-garmin-connect/Calendar.go b/python-garmin-connect/Calendar.go
deleted file mode 100644
index 09c4a7f..0000000
--- a/python-garmin-connect/Calendar.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package connect
-
-import (
- "fmt"
-)
-
-// CalendarYear describes a Garmin Connect calendar year
-type CalendarYear struct {
- StartDayOfJanuary int `json:"startDayofJanuary"`
- LeapYear bool `json:"leapYear"`
- YearItems []YearItem `json:"yearItems"`
- YearSummaries []YearSummary `json:"yearSummaries"`
-}
-
-// YearItem describes an item on a Garmin Connect calendar year
-type YearItem struct {
- Date Date `json:"date"`
- Display int `json:"display"`
-}
-
-// YearSummary describes a per-activity-type yearly summary on a Garmin Connect calendar year
-type YearSummary struct {
- ActivityTypeID int `json:"activityTypeId"`
- NumberOfActivities int `json:"numberOfActivities"`
- TotalDistance int `json:"totalDistance"`
- TotalDuration int `json:"totalDuration"`
- TotalCalories int `json:"totalCalories"`
-}
-
-// CalendarMonth describes a Garmin Conenct calendar month
-type CalendarMonth struct {
- StartDayOfMonth int `json:"startDayOfMonth"`
- NumOfDaysInMonth int `json:"numOfDaysInMonth"`
- NumOfDaysInPrevMonth int `json:"numOfDaysInPrevMonth"`
- Month int `json:"month"`
- Year int `json:"year"`
- CalendarItems []CalendarItem `json:"calendarItems"`
-}
-
-// CalendarWeek describes a Garmin Connect calendar week
-type CalendarWeek struct {
- StartDate Date `json:"startDate"`
- EndDate Date `json:"endDate"`
- NumOfDaysInMonth int `json:"numOfDaysInMonth"`
- CalendarItems []CalendarItem `json:"calendarItems"`
-}
-
-// CalendarItem describes an activity displayed on a Garmin Connect calendar
-type CalendarItem struct {
- ID int `json:"id"`
- ItemType string `json:"itemType"`
- ActivityTypeID int `json:"activityTypeId"`
- Title string `json:"title"`
- Date Date `json:"date"`
- Duration int `json:"duration"`
- Distance int `json:"distance"`
- Calories int `json:"calories"`
- StartTimestampLocal Time `json:"startTimestampLocal"`
- ElapsedDuration float64 `json:"elapsedDuration"`
- Strokes float64 `json:"strokes"`
- MaxSpeed float64 `json:"maxSpeed"`
- ShareableEvent bool `json:"shareableEvent"`
- AutoCalcCalories bool `json:"autoCalcCalories"`
- ProtectedWorkoutSchedule bool `json:"protectedWorkoutSchedule"`
- IsParent bool `json:"isParent"`
-}
-
-// CalendarYear will get the activity summaries and list of days active for a given year
-func (c *Client) CalendarYear(year int) (*CalendarYear, error) {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/calendar-service/year/%d",
- year,
- )
- calendarYear := new(CalendarYear)
- err := c.getJSON(URL, &calendarYear)
- if err != nil {
- return nil, err
- }
-
- return calendarYear, nil
-}
-
-// CalendarMonth will get the activities for a given month
-func (c *Client) CalendarMonth(year int, month int) (*CalendarMonth, error) {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/calendar-service/year/%d/month/%d",
- year,
- month-1, // Months in Garmin Connect start from zero
- )
- calendarMonth := new(CalendarMonth)
- err := c.getJSON(URL, &calendarMonth)
- if err != nil {
- return nil, err
- }
-
- return calendarMonth, nil
-}
-
-// CalendarWeek will get the activities for a given week. A week will be returned that contains the day requested, not starting with)
-func (c *Client) CalendarWeek(year int, month int, week int) (*CalendarWeek, error) {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/calendar-service/year/%d/month/%d/day/%d/start/1",
- year,
- month-1, // Months in Garmin Connect start from zero
- week,
- )
- calendarWeek := new(CalendarWeek)
- err := c.getJSON(URL, &calendarWeek)
- if err != nil {
- return nil, err
- }
-
- return calendarWeek, nil
-}
diff --git a/python-garmin-connect/Client.go b/python-garmin-connect/Client.go
deleted file mode 100644
index b3f62a6..0000000
--- a/python-garmin-connect/Client.go
+++ /dev/null
@@ -1,615 +0,0 @@
-package connect
-
-import (
- "bufio"
- "bytes"
- "crypto/tls"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "net/http/httputil"
- "net/url"
- "regexp"
- "strings"
- "time"
-)
-
-const (
- // ErrForbidden will be returned if the client doesn't have access to the
- // requested ressource.
- ErrForbidden = Error("forbidden")
-
- // ErrNotFound will be returned if the requested ressource could not be
- // found.
- ErrNotFound = Error("not found")
-
- // ErrBadRequest will be returned if Garmin returned a status code 400.
- ErrBadRequest = Error("bad request")
-
- // ErrNoCredentials will be returned if credentials are needed - but none
- // are set.
- ErrNoCredentials = Error("no credentials set")
-
- // ErrNotAuthenticated will be returned is the client is not
- // authenticated as required by the request. Remember to call
- // Authenticate().
- ErrNotAuthenticated = Error("client is not authenticated")
-
- // ErrWrongCredentials will be returned if the username and/or
- // password is not recognized by Garmin Connect.
- ErrWrongCredentials = Error("username and/or password not recognized")
-)
-
-const (
- // sessionCookieName is the magic session cookie name.
- sessionCookieName = "SESSIONID"
-
- // cflbCookieName is the cookie used by Cloudflare to pin the request
- // to a specific backend.
- cflbCookieName = "__cflb"
-)
-
-// Client can be used to access the unofficial Garmin Connect API.
-type Client struct {
- Email string `json:"email"`
- Password string `json:"password"`
- SessionID string `json:"sessionID"`
- Profile *SocialProfile `json:"socialProfile"`
-
- // LoadBalancerID is the load balancer ID set by Cloudflare in front of
- // Garmin Connect. This must be preserves across requests. A session key
- // is only valid with a corresponding loadbalancer key.
- LoadBalancerID string `json:"cflb"`
-
- client *http.Client
- autoRenewSession bool
- debugLogger Logger
- dumpWriter io.Writer
-}
-
-// Option is the type to set options on the client.
-type Option func(*Client)
-
-// SessionID will set a predefined session ID. This can be useful for clients
-// keeping state. A few HTTP roundtrips can be saved, if the session ID is
-// reused. And some load would be taken of Garmin servers. This must be
-// accompanied by LoadBalancerID.
-// Generally this should not be used. Users of this package should save
-// all exported fields from Client and re-use those at a later request.
-// json.Marshal() and json.Unmarshal() can be used.
-func SessionID(sessionID string) Option {
- return func(c *Client) {
- c.SessionID = sessionID
- }
-}
-
-// LoadBalancerID will set a load balancer ID. This is used by Garmin load
-// balancers to route subsequent requests to the same backend server.
-func LoadBalancerID(loadBalancerID string) Option {
- return func(c *Client) {
- c.LoadBalancerID = loadBalancerID
- }
-}
-
-// Credentials can be used to pass login credentials to NewClient.
-func Credentials(email string, password string) Option {
- return func(c *Client) {
- c.Email = email
- c.Password = password
- }
-}
-
-// AutoRenewSession will set if the session should be autorenewed upon expire.
-// Default is true.
-func AutoRenewSession(autoRenew bool) Option {
- return func(c *Client) {
- c.autoRenewSession = autoRenew
- }
-}
-
-// DebugLogger is used to set a debug logger.
-func DebugLogger(logger Logger) Option {
- return func(c *Client) {
- c.debugLogger = logger
- }
-}
-
-// DumpWriter will instruct Client to dump all HTTP requests and responses to
-// and from Garmin to w.
-func DumpWriter(w io.Writer) Option {
- return func(c *Client) {
- c.dumpWriter = w
- }
-}
-
-// NewClient returns a new client for accessing the unofficial Garmin Connect
-// API.
-func NewClient(options ...Option) *Client {
- client := &Client{
- client: &http.Client{
- CheckRedirect: func(req *http.Request, via []*http.Request) error {
- return http.ErrUseLastResponse
- },
-
- Transport: &http.Transport{
- TLSClientConfig: &tls.Config{
- // To avoid a Cloudflare error, we have to use TLS 1.1 or 1.2.
- MinVersion: tls.VersionTLS11,
- MaxVersion: tls.VersionTLS12,
- },
- },
- },
- autoRenewSession: true,
- debugLogger: &discardLog{},
- dumpWriter: nil,
- }
-
- client.SetOptions(options...)
-
- return client
-}
-
-// SetOptions can be used to set various options on Client.
-func (c *Client) SetOptions(options ...Option) {
- for _, option := range options {
- option(c)
- }
-}
-
-func (c *Client) dump(reqResp interface{}) {
- if c.dumpWriter == nil {
- return
- }
-
- var dump []byte
- switch obj := reqResp.(type) {
- case *http.Request:
- _, _ = c.dumpWriter.Write([]byte("\n\nREQUEST\n"))
- dump, _ = httputil.DumpRequestOut(obj, true)
- case *http.Response:
- _, _ = c.dumpWriter.Write([]byte("\n\nRESPONSE\n"))
- dump, _ = httputil.DumpResponse(obj, true)
- default:
- panic("unsupported type")
- }
-
- _, _ = c.dumpWriter.Write(dump)
-}
-
-// addCookies adds needed cookies to a http request if the values are known.
-func (c *Client) addCookies(req *http.Request) {
- if c.SessionID != "" {
- req.AddCookie(&http.Cookie{
- Value: c.SessionID,
- Name: sessionCookieName,
- })
- }
-
- if c.LoadBalancerID != "" {
- req.AddCookie(&http.Cookie{
- Value: c.LoadBalancerID,
- Name: cflbCookieName,
- })
- }
-}
-
-func (c *Client) newRequest(method string, url string, body io.Reader) (*http.Request, error) {
- req, err := http.NewRequest(method, url, body)
- if err != nil {
- return nil, err
- }
-
- // Play nice and give Garmin engineers a way to contact us.
- req.Header.Set("User-Agent", "github.com/abrander/garmin-connect")
-
- // Yep. This is needed for requests sent to the API. No idea what it does.
- req.Header.Add("nk", "NT")
-
- c.addCookies(req)
-
- return req, nil
-}
-
-func (c *Client) getJSON(url string, target interface{}) error {
- req, err := c.newRequest("GET", url, nil)
- if err != nil {
- return err
- }
-
- resp, err := c.do(req)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
-
- decoder := json.NewDecoder(resp.Body)
-
- return decoder.Decode(target)
-}
-
-// write is suited for writing stuff to the API when you're NOT expected any
-// data in return but a HTTP status code.
-func (c *Client) write(method string, url string, payload interface{}, expectedStatus int) error {
- var body io.Reader
-
- if payload != nil {
- b, err := json.Marshal(payload)
- if err != nil {
- return err
- }
-
- body = bytes.NewReader(b)
- }
-
- req, err := c.newRequest(method, url, body)
- if err != nil {
- return err
- }
-
- // If we have a payload it is by definition JSON.
- if payload != nil {
- req.Header.Add("content-type", "application/json")
- }
-
- resp, err := c.do(req)
- if err != nil {
- return err
- }
- resp.Body.Close()
-
- if expectedStatus > 0 && resp.StatusCode != expectedStatus {
- return fmt.Errorf("HTTP %s returned %d (%d expected)", method, resp.StatusCode, expectedStatus)
- }
-
- return nil
-}
-
-// handleForbidden will try to extract an error message from the response.
-func (c *Client) handleForbidden(resp *http.Response) error {
- defer resp.Body.Close()
-
- type proxy struct {
- Message string `json:"message"`
- Error string `json:"error"`
- }
-
- decoder := json.NewDecoder(resp.Body)
-
- var errorMessage proxy
-
- err := decoder.Decode(&errorMessage)
- if err == nil && errorMessage.Message != "" {
- return Error(errorMessage.Message)
- }
-
- return ErrForbidden
-}
-
-func (c *Client) do(req *http.Request) (*http.Response, error) {
- c.debugLogger.Printf("Requesting %s at %s", req.Method, req.URL.String())
-
- // Save the body in case we need to replay the request.
- var save io.ReadCloser
- var err error
- if req.Body != nil {
- save, req.Body, err = drainBody(req.Body)
- if err != nil {
- return nil, err
- }
- }
-
- c.dump(req)
- t0 := time.Now()
- resp, err := c.client.Do(req)
- if err != nil {
- return nil, err
- }
- c.dump(resp)
-
- // This is exciting. If the user does not have permission to access a
- // ressource, the API will return an ApplicationException and return a
- // 403 status code.
- // If the session is invalid, the Garmin API will return the same exception
- // and status code (!).
- // To distinguish between these two error cases, we look for a new session
- // cookie in the response. If a new session cookies is set by Garmin, we
- // assume our current session is invalid.
- for _, cookie := range resp.Cookies() {
- if cookie.Name == sessionCookieName {
- resp.Body.Close()
- c.debugLogger.Printf("Session invalid, requesting new session")
-
- // Wups. Our session got invalidated.
- c.SetOptions(SessionID(""))
- c.SetOptions(LoadBalancerID(""))
-
- // Re-new session.
- err = c.Authenticate()
- if err != nil {
- return nil, err
- }
-
- c.debugLogger.Printf("Successfully authenticated as %s", c.Email)
-
- // Replace the drained body
- req.Body = save
-
- // Replace the cookie ned newRequest with the new sessionid and load balancer key.
- req.Header.Del("Cookie")
- c.addCookies(req)
-
- c.debugLogger.Printf("Replaying %s request to %s", req.Method, req.URL.String())
-
- c.dump(req)
-
- // Replay the original request only once, if we fail twice
- // something is rotten, and we should give up.
- t0 = time.Now()
- resp, err = c.client.Do(req)
- if err != nil {
- return nil, err
- }
- c.dump(resp)
- }
- }
-
- c.debugLogger.Printf("Got HTTP status code %d in %s", resp.StatusCode, time.Since(t0).String())
-
- switch resp.StatusCode {
- case http.StatusBadRequest:
- resp.Body.Close()
- return nil, ErrBadRequest
- case http.StatusForbidden:
- return nil, c.handleForbidden(resp)
- case http.StatusNotFound:
- resp.Body.Close()
- return nil, ErrNotFound
- }
-
- return resp, err
-}
-
-// Download will retrieve a file from url using Garmin Connect credentials.
-// It's mostly useful when developing new features or debugging existing
-// ones.
-// Please note that this will pass the Garmin session cookie to the URL
-// provided. Only use this for endpoints on garmin.com.
-func (c *Client) Download(url string, w io.Writer) error {
- req, err := c.newRequest("GET", url, nil)
- if err != nil {
- return err
- }
-
- resp, err := c.do(req)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
-
- _, err = io.Copy(w, resp.Body)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (c *Client) authenticated() bool {
- return c.SessionID != ""
-}
-
-// Authenticate using a Garmin Connect username and password provided by
-// the Credentials option function.
-func (c *Client) Authenticate() error {
- // We cannot use Client.do() in this function, since this function can be
- // called from do() upon session renewal.
- URL := "https://sso.garmin.com/sso/signin" +
- "?service=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F" +
- "&gauthHost=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F" +
- "&generateExtraServiceTicket=true" +
- "&generateTwoExtraServiceTickets=true"
-
- if c.Email == "" || c.Password == "" {
- return ErrNoCredentials
- }
-
- c.debugLogger.Printf("Getting CSRF token at %s", URL)
-
- // Start by getting CSRF token.
- req, err := http.NewRequest("GET", URL, nil)
- if err != nil {
- return err
- }
- c.dump(req)
- resp, err := c.client.Do(req)
- if err != nil {
- return err
- }
- c.dump(resp)
-
- csrfToken, err := extractCSRFToken(resp.Body)
- if err != nil {
- return err
- }
- resp.Body.Close()
-
- c.debugLogger.Printf("Got CSRF token: '%s'", csrfToken)
-
- c.debugLogger.Printf("Trying credentials at %s", URL)
-
- formValues := url.Values{
- "username": {c.Email},
- "password": {c.Password},
- "embed": {"false"},
- "_csrf": {csrfToken},
- }
-
- req, err = c.newRequest("POST", URL, strings.NewReader(formValues.Encode()))
- if err != nil {
- return nil
- }
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- req.Header.Set("Referer", URL)
-
- c.dump(req)
-
- resp, err = c.client.Do(req)
- if err != nil {
- return err
- }
- c.dump(resp)
-
- if resp.StatusCode != http.StatusOK {
- resp.Body.Close()
-
- return fmt.Errorf("Garmin SSO returned \"%s\"", resp.Status)
- }
-
- body, _ := ioutil.ReadAll(resp.Body)
- resp.Body.Close()
-
- // Extract ticket URL
- t := regexp.MustCompile(`https:\\\/\\\/connect.garmin.com\\\/modern\\\/\?ticket=(([a-zA-Z0-9]|-)*)`)
- ticketURL := t.FindString(string(body))
-
- // undo escaping
- ticketURL = strings.Replace(ticketURL, "\\/", "/", -1)
-
- if ticketURL == "" {
- return ErrWrongCredentials
- }
-
- c.debugLogger.Printf("Requesting session at ticket URL %s", ticketURL)
-
- // Use ticket to request session.
- req, _ = c.newRequest("GET", ticketURL, nil)
- c.dump(req)
- resp, err = c.client.Do(req)
- if err != nil {
- return err
- }
- c.dump(resp)
- resp.Body.Close()
-
- // Look for the needed sessionid cookie.
- for _, cookie := range resp.Cookies() {
- if cookie.Name == cflbCookieName {
- c.debugLogger.Printf("Found load balancer cookie with value %s", cookie.Value)
-
- c.SetOptions(LoadBalancerID(cookie.Value))
- }
-
- if cookie.Name == sessionCookieName {
- c.debugLogger.Printf("Found session cookie with value %s", cookie.Value)
-
- c.SetOptions(SessionID(cookie.Value))
- }
- }
-
- if c.SessionID == "" {
- c.debugLogger.Printf("No sessionid found")
-
- return ErrWrongCredentials
- }
-
- // The session id will not be valid until we redeem the sessions by
- // following the redirect.
- location := resp.Header.Get("Location")
- c.debugLogger.Printf("Redeeming session id at %s", location)
-
- req, _ = c.newRequest("GET", location, nil)
- c.dump(req)
- resp, err = c.client.Do(req)
- if err != nil {
- return err
- }
- c.dump(resp)
-
- c.Profile, err = extractSocialProfile(resp.Body)
- if err != nil {
- return err
- }
-
- resp.Body.Close()
-
- return nil
-}
-
-// extractSocialProfile will try to extract the social profile from the HTML.
-// This is very fragile.
-func extractSocialProfile(body io.Reader) (*SocialProfile, error) {
- scanner := bufio.NewScanner(body)
-
- for scanner.Scan() {
- line := scanner.Text()
- if strings.Contains(line, "VIEWER_SOCIAL_PROFILE") {
- line = strings.TrimSpace(line)
- line = strings.Replace(line, "\\", "", -1)
- line = strings.TrimPrefix(line, "window.VIEWER_SOCIAL_PROFILE = ")
- line = strings.TrimSuffix(line, ";")
-
- profile := new(SocialProfile)
-
- err := json.Unmarshal([]byte(line), profile)
- if err != nil {
- return nil, err
- }
-
- return profile, nil
- }
- }
-
- return nil, errors.New("social profile not found in HTML")
-}
-
-// extractCSRFToken will try to extract the CSRF token from the signin form.
-// This is very fragile. Maybe we should replace this madness by a real HTML
-// parser some day.
-func extractCSRFToken(body io.Reader) (string, error) {
- scanner := bufio.NewScanner(body)
-
- for scanner.Scan() {
- line := scanner.Text()
- if strings.Contains(line, "name=\"_csrf\"") {
- line = strings.TrimSpace(line)
- line = strings.TrimPrefix(line, ``)
-
- return line, nil
- }
- }
-
- return "", errors.New("CSRF token not found")
-}
-
-// Signout will end the session with Garmin. If you use this for regular
-// automated tasks, it would be nice to signout each time to avoid filling
-// Garmin's session tables with a lot of short-lived sessions.
-func (c *Client) Signout() error {
- if !c.authenticated() {
- return ErrNotAuthenticated
- }
-
- req, err := c.newRequest("GET", "https://connect.garmin.com/modern/auth/logout", nil)
- if err != nil {
- return err
- }
-
- if c.SessionID == "" {
- return nil
- }
-
- resp, err := c.client.Do(req)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
-
- c.SetOptions(SessionID(""))
- c.SetOptions(LoadBalancerID(""))
-
- return nil
-}
diff --git a/python-garmin-connect/Connections.go b/python-garmin-connect/Connections.go
deleted file mode 100644
index 0253a3c..0000000
--- a/python-garmin-connect/Connections.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package connect
-
-import (
- "encoding/json"
- "fmt"
- "net/url"
- "strings"
-)
-
-// Connections will list the connections of displayName. If displayName is
-// empty, the current authenticated users connection list wil be returned.
-func (c *Client) Connections(displayName string) ([]SocialProfile, error) {
- // There also exist an endpoint without /pagination/ but it will return
- // 403 for *some* connections.
- URL := "https://connect.garmin.com/modern/proxy/userprofile-service/socialProfile/connections/pagination/" + displayName
-
- if !c.authenticated() && displayName == "" {
- return nil, ErrNotAuthenticated
- }
-
- var proxy struct {
- Connections []SocialProfile `json:"userConnections"`
- }
-
- err := c.getJSON(URL, &proxy)
- if err != nil {
- return nil, err
- }
-
- return proxy.Connections, nil
-}
-
-// PendingConnections returns a list of pending connections.
-func (c *Client) PendingConnections() ([]SocialProfile, error) {
- URL := "https://connect.garmin.com/modern/proxy/userprofile-service/connection/pending"
-
- if !c.authenticated() {
- return nil, ErrNotAuthenticated
- }
-
- pending := make([]SocialProfile, 0, 10)
-
- err := c.getJSON(URL, &pending)
- if err != nil {
- return nil, err
- }
-
- return pending, nil
-}
-
-// AcceptConnection will accept a pending connection.
-func (c *Client) AcceptConnection(connectionRequestID int) error {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/userprofile-service/connection/accept/%d", connectionRequestID)
- payload := struct {
- ConnectionRequestID int `json:"connectionRequestId"`
- }{
- ConnectionRequestID: connectionRequestID,
- }
-
- return c.write("PUT", URL, payload, 0)
-}
-
-// SearchConnections can search other users of Garmin Connect.
-func (c *Client) SearchConnections(keyword string) ([]SocialProfile, error) {
- URL := "https://connect.garmin.com/modern/proxy/usersearch-service/search"
-
- payload := url.Values{
- "start": {"1"},
- "limit": {"20"},
- "keyword": {keyword},
- }
-
- req, err := c.newRequest("POST", URL, strings.NewReader(payload.Encode()))
- if err != nil {
- return nil, err
- }
-
- req.Header.Add("content-type", "application/x-www-form-urlencoded; charset=UTF-8")
-
- resp, err := c.do(req)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
-
- var proxy struct {
- Profiles []SocialProfile `json:"profileList"`
- }
-
- dec := json.NewDecoder(resp.Body)
- err = dec.Decode(&proxy)
- if err != nil {
- return nil, err
- }
-
- return proxy.Profiles, nil
-}
-
-// RemoveConnection will remove a connection.
-func (c *Client) RemoveConnection(connectionRequestID int) error {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/userprofile-service/connection/end/%d", connectionRequestID)
-
- return c.write("PUT", URL, nil, 200)
-}
-
-// RequestConnection will request a connection with displayName.
-func (c *Client) RequestConnection(displayName string) error {
- URL := "https://connect.garmin.com/modern/proxy/userprofile-service/connection/request/" + displayName
-
- return c.write("PUT", URL, nil, 0)
-}
diff --git a/python-garmin-connect/DailyStress.go b/python-garmin-connect/DailyStress.go
deleted file mode 100644
index 16825da..0000000
--- a/python-garmin-connect/DailyStress.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package connect
-
-import (
- "fmt"
- "time"
-)
-
-// StressPoint is a measured stress level at a point in time.
-type StressPoint struct {
- Timestamp time.Time
- Value int
-}
-
-// DailyStress is a stress reading for a single day.
-type DailyStress struct {
- UserProfilePK int `json:"userProfilePK"`
- CalendarDate string `json:"calendarDate"`
- StartGMT Time `json:"startTimestampGMT"`
- EndGMT Time `json:"endTimestampGMT"`
- StartLocal Time `json:"startTimestampLocal"`
- EndLocal Time `json:"endTimestampLocal"`
- Max int `json:"maxStressLevel"`
- Average int `json:"avgStressLevel"`
- Values []StressPoint
-}
-
-// DailyStress will retrieve stress levels for date.
-func (c *Client) DailyStress(date time.Time) (*DailyStress, error) {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/wellness-service/wellness/dailyStress/%s",
- formatDate(date))
-
- if !c.authenticated() {
- return nil, ErrNotAuthenticated
- }
-
- // We use a proxy object to deserialize the values to proper Go types.
- var proxy struct {
- DailyStress
- StressValuesArray [][2]int64 `json:"stressValuesArray"`
- }
-
- err := c.getJSON(URL, &proxy)
- if err != nil {
- return nil, err
- }
-
- ret := &proxy.DailyStress
- ret.Values = make([]StressPoint, len(proxy.StressValuesArray))
-
- for i, point := range proxy.StressValuesArray {
- ret.Values[i].Timestamp = time.Unix(point[0]/1000, 0)
- ret.Values[i].Value = int(point[1])
- }
-
- return &proxy.DailyStress, nil
-}
diff --git a/python-garmin-connect/DailySummary.go b/python-garmin-connect/DailySummary.go
deleted file mode 100644
index 6d0866c..0000000
--- a/python-garmin-connect/DailySummary.go
+++ /dev/null
@@ -1,189 +0,0 @@
-package connect
-
-import (
- "fmt"
- "time"
-)
-
-// DateValue is a numeric value recorded on a given date.
-type DateValue struct {
- Date Date `json:"calendarDate"`
- Value float64 `json:"value"`
-}
-
-// DailySummaries provides a daily summary of various statistics for multiple
-// days.
-type DailySummaries struct {
- Start time.Time `json:"statisticsStartDate"`
- End time.Time `json:"statisticsEndDate"`
- TotalSteps []DateValue `json:"WELLNESS_TOTAL_STEPS"`
- ActiveCalories []DateValue `json:"COMMON_ACTIVE_CALORIES"`
- FloorsAscended []DateValue `json:"WELLNESS_FLOORS_ASCENDED"`
- IntensityMinutes []DateValue `json:"WELLNESS_USER_INTENSITY_MINUTES_GOAL"`
- MaxHeartRate []DateValue `json:"WELLNESS_MAX_HEART_RATE"`
- MinimumAverageHeartRate []DateValue `json:"WELLNESS_MIN_AVG_HEART_RATE"`
- MinimumHeartrate []DateValue `json:"WELLNESS_MIN_HEART_RATE"`
- AverageStress []DateValue `json:"WELLNESS_AVERAGE_STRESS"`
- RestingHeartRate []DateValue `json:"WELLNESS_RESTING_HEART_RATE"`
- MaxStress []DateValue `json:"WELLNESS_MAX_STRESS"`
- AbnormalHeartRateAlers []DateValue `json:"WELLNESS_ABNORMALHR_ALERTS_COUNT"`
- MaximumAverageHeartRate []DateValue `json:"WELLNESS_MAX_AVG_HEART_RATE"`
- StepGoal []DateValue `json:"WELLNESS_TOTAL_STEP_GOAL"`
- FlorsAscendedGoal []DateValue `json:"WELLNESS_USER_FLOORS_ASCENDED_GOAL"`
- ModerateIntensityMinutes []DateValue `json:"WELLNESS_MODERATE_INTENSITY_MINUTES"`
- TotalColaries []DateValue `json:"WELLNESS_TOTAL_CALORIES"`
- BodyBatteryCharged []DateValue `json:"WELLNESS_BODYBATTERY_CHARGED"`
- FloorsDescended []DateValue `json:"WELLNESS_FLOORS_DESCENDED"`
- BMRCalories []DateValue `json:"WELLNESS_BMR_CALORIES"`
- FoodCaloriesRemainin []DateValue `json:"FOOD_CALORIES_REMAINING"`
- TotalCalories []DateValue `json:"COMMON_TOTAL_CALORIES"`
- BodyBatteryDrained []DateValue `json:"WELLNESS_BODYBATTERY_DRAINED"`
- AverageSteps []DateValue `json:"WELLNESS_AVERAGE_STEPS"`
- VigorousIntensifyMinutes []DateValue `json:"WELLNESS_VIGOROUS_INTENSITY_MINUTES"`
- WellnessDistance []DateValue `json:"WELLNESS_TOTAL_DISTANCE"`
- Distance []DateValue `json:"COMMON_TOTAL_DISTANCE"`
- WellnessActiveCalories []DateValue `json:"WELLNESS_ACTIVE_CALORIES"`
-}
-
-// DailySummary is an extensive summary for a single day.
-type DailySummary struct {
- ProfileID int64 `json:"userProfileId"`
- TotalKilocalories float64 `json:"totalKilocalories"`
- ActiveKilocalories float64 `json:"activeKilocalories"`
- BMRKilocalories float64 `json:"bmrKilocalories"`
- WellnessKilocalories float64 `json:"wellnessKilocalories"`
- BurnedKilocalories float64 `json:"burnedKilocalories"`
- ConsumedKilocalories float64 `json:"consumedKilocalories"`
- RemainingKilocalories float64 `json:"remainingKilocalories"`
- TotalSteps int `json:"totalSteps"`
- NetCalorieGoal float64 `json:"netCalorieGoal"`
- TotalDistanceMeters int `json:"totalDistanceMeters"`
- WellnessDistanceMeters int `json:"wellnessDistanceMeters"`
- WellnessActiveKilocalories float64 `json:"wellnessActiveKilocalories"`
- NetRemainingKilocalories float64 `json:"netRemainingKilocalories"`
- UserID int64 `json:"userDailySummaryId"`
- Date Date `json:"calendarDate"`
- UUID string `json:"uuid"`
- StepGoal int `json:"dailyStepGoal"`
- StartTimeGMT Time `json:"wellnessStartTimeGmt"`
- EndTimeGMT Time `json:"wellnessEndTimeGmt"`
- StartLocal Time `json:"wellnessStartTimeLocal"`
- EndLocal Time `json:"wellnessEndTimeLocal"`
- Duration time.Duration `json:"durationInMilliseconds"`
- Description string `json:"wellnessDescription"`
- HighlyActive time.Duration `json:"highlyActiveSeconds"`
- Active time.Duration `json:"activeSeconds"`
- Sedentary time.Duration `json:"sedentarySeconds"`
- Sleeping time.Duration `json:"sleepingSeconds"`
- IncludesWellnessData bool `json:"includesWellnessData"`
- IncludesActivityData bool `json:"includesActivityData"`
- IncludesCalorieConsumedData bool `json:"includesCalorieConsumedData"`
- PrivacyProtected bool `json:"privacyProtected"`
- ModerateIntensity time.Duration `json:"moderateIntensityMinutes"`
- VigorousIntensity time.Duration `json:"vigorousIntensityMinutes"`
- FloorsAscendedInMeters float64 `json:"floorsAscendedInMeters"`
- FloorsDescendedInMeters float64 `json:"floorsDescendedInMeters"`
- FloorsAscended float64 `json:"floorsAscended"`
- FloorsDescended float64 `json:"floorsDescended"`
- IntensityGoal time.Duration `json:"intensityMinutesGoal"`
- FloorsAscendedGoal int `json:"userFloorsAscendedGoal"`
- MinHeartRate int `json:"minHeartRate"`
- MaxHeartRate int `json:"maxHeartRate"`
- RestingHeartRate int `json:"restingHeartRate"`
- LastSevenDaysAvgRestingHeartRate int `json:"lastSevenDaysAvgRestingHeartRate"`
- Source string `json:"source"`
- AverageStress int `json:"averageStressLevel"`
- MaxStress int `json:"maxStressLevel"`
- Stress time.Duration `json:"stressDuration"`
- RestStress time.Duration `json:"restStressDuration"`
- ActivityStress time.Duration `json:"activityStressDuration"`
- UncategorizedStress time.Duration `json:"uncategorizedStressDuration"`
- TotalStress time.Duration `json:"totalStressDuration"`
- LowStress time.Duration `json:"lowStressDuration"`
- MediumStress time.Duration `json:"mediumStressDuration"`
- HighStress time.Duration `json:"highStressDuration"`
- StressQualifier string `json:"stressQualifier"`
- MeasurableAwake time.Duration `json:"measurableAwakeDuration"`
- MeasurableAsleep time.Duration `json:"measurableAsleepDuration"`
- LastSyncGMT Time `json:"lastSyncTimestampGMT"`
- MinAverageHeartRate int `json:"minAvgHeartRate"`
- MaxAverageHeartRate int `json:"maxAvgHeartRate"`
-}
-
-// DailySummary will retrieve a detailed daily summary for date. If
-// displayName is empty, the currently authenticated user will be used.
-func (c *Client) DailySummary(displayName string, date time.Time) (*DailySummary, error) {
- if displayName == "" && c.Profile == nil {
- return nil, ErrNotAuthenticated
- }
-
- if displayName == "" && c.Profile != nil {
- displayName = c.Profile.DisplayName
- }
-
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/usersummary-service/usersummary/daily/%s?calendarDate=%s",
- displayName,
- formatDate(date),
- )
-
- summary := new(DailySummary)
-
- err := c.getJSON(URL, summary)
- if err != nil {
- return nil, err
- }
-
- summary.Duration *= time.Millisecond
- summary.HighlyActive *= time.Second
- summary.Active *= time.Second
- summary.Sedentary *= time.Second
- summary.Sleeping *= time.Second
- summary.ModerateIntensity *= time.Minute
- summary.VigorousIntensity *= time.Minute
- summary.IntensityGoal *= time.Minute
- summary.Stress *= time.Second
- summary.RestStress *= time.Second
- summary.ActivityStress *= time.Second
- summary.UncategorizedStress *= time.Second
- summary.TotalStress *= time.Second
- summary.LowStress *= time.Second
- summary.MediumStress *= time.Second
- summary.HighStress *= time.Second
- summary.MeasurableAwake *= time.Second
- summary.MeasurableAsleep *= time.Second
-
- return summary, nil
-}
-
-// DailySummaries will retrieve a daily summary for userID.
-func (c *Client) DailySummaries(userID string, from time.Time, until time.Time) (*DailySummaries, error) {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/userstats-service/wellness/daily/%s?fromDate=%s&untilDate=%s",
- userID,
- formatDate(from),
- formatDate(until),
- )
-
- if !c.authenticated() {
- return nil, ErrNotAuthenticated
- }
-
- // We use a proxy object to deserialize the values to proper Go types.
- var proxy struct {
- Start Date `json:"statisticsStartDate"`
- End Date `json:"statisticsEndDate"`
- AllMetrics struct {
- Summary DailySummaries `json:"metricsMap"`
- } `json:"allMetrics"`
- }
-
- err := c.getJSON(URL, &proxy)
- if err != nil {
- return nil, err
- }
-
- ret := &proxy.AllMetrics.Summary
- ret.Start = proxy.Start.Time()
- ret.End = proxy.End.Time()
-
- return ret, nil
-}
diff --git a/python-garmin-connect/Date.go b/python-garmin-connect/Date.go
deleted file mode 100644
index e4a1e81..0000000
--- a/python-garmin-connect/Date.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package connect
-
-import (
- "encoding/json"
- "fmt"
- "strconv"
- "time"
-)
-
-// Date represents a single day in Garmin Connect.
-type Date struct {
- Year int
- Month time.Month
- DayOfMonth int
-}
-
-// Time returns a time.Time for usage in other packages.
-func (d Date) Time() time.Time {
- return time.Date(d.Year, d.Month, d.DayOfMonth, 0, 0, 0, 0, time.UTC)
-}
-
-// UnmarshalJSON implements json.Unmarshaler.
-func (d *Date) UnmarshalJSON(value []byte) error {
- if string(value) == "null" {
- return nil
- }
-
- // Sometimes dates are transferred as milliseconds since epoch :-/
- i, err := strconv.ParseInt(string(value), 10, 64)
- if err == nil {
- t := time.Unix(i/1000, 0)
-
- d.Year, d.Month, d.DayOfMonth = t.Date()
-
- return nil
- }
-
- var blip string
- err = json.Unmarshal(value, &blip)
- if err != nil {
- return err
- }
-
- _, err = fmt.Sscanf(blip, "%04d-%02d-%02d", &d.Year, &d.Month, &d.DayOfMonth)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-// MarshalJSON implements json.Marshaler.
-func (d Date) MarshalJSON() ([]byte, error) {
- // To better support the Garmin API we marshal the empty value as null.
- if d.Year == 0 && d.Month == 0 && d.DayOfMonth == 0 {
- return []byte("null"), nil
- }
-
- return []byte(fmt.Sprintf("\"%04d-%02d-%02d\"", d.Year, d.Month, d.DayOfMonth)), nil
-}
-
-// ParseDate will parse a date in the format yyyy-mm-dd.
-func ParseDate(in string) (Date, error) {
- d := Date{}
-
- _, err := fmt.Sscanf(in, "%04d-%02d-%02d", &d.Year, &d.Month, &d.DayOfMonth)
-
- return d, err
-}
-
-// String implements Stringer.
-func (d Date) String() string {
- if d.Year == 0 && d.Month == 0 && d.DayOfMonth == 0 {
- return "-"
- }
-
- return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.DayOfMonth)
-}
-
-// Today will return a Date set to today.
-func Today() Date {
- d := Date{}
-
- d.Year, d.Month, d.DayOfMonth = time.Now().Date()
-
- return d
-}
diff --git a/python-garmin-connect/Error.go b/python-garmin-connect/Error.go
deleted file mode 100644
index f40853d..0000000
--- a/python-garmin-connect/Error.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package connect
-
-// Error is a type implementing the error interface. We use this to define
-// constant errors.
-type Error string
-
-// Error implements error.
-func (e Error) Error() string {
- return string(e)
-}
diff --git a/python-garmin-connect/Gear.go b/python-garmin-connect/Gear.go
deleted file mode 100644
index 6cc2f6c..0000000
--- a/python-garmin-connect/Gear.go
+++ /dev/null
@@ -1,131 +0,0 @@
-package connect
-
-import (
- "fmt"
-)
-
-// Gear describes a Garmin Connect gear entry
-type Gear struct {
- Uuid string `json:"uuid"`
- GearPk int `json:"gearPk"`
- UserProfileID int64 `json:"userProfilePk"`
- GearMakeName string `json:"gearMakeName"`
- GearModelName string `json:"gearModelName"`
- GearTypeName string `json:"gearTypeName"`
- DisplayName string `json:"displayName"`
- CustomMakeModel string `json:"customMakeModel"`
- ImageNameLarge string `json:"imageNameLarge"`
- ImageNameMedium string `json:"imageNameMedium"`
- ImageNameSmall string `json:"imageNameSmall"`
- DateBegin Time `json:"dateBegin"`
- DateEnd Time `json:"dateEnd"`
- MaximumMeters float64 `json:"maximumMeters"`
- Notified bool `json:"notified"`
- CreateDate Time `json:"createDate"`
- UpdateDate Time `json:"updateDate"`
-}
-
-// GearType desribes the types of gear
-type GearType struct {
- TypeID int `json:"gearTypePk"`
- TypeName string `json:"gearTypeName"`
- CreateDate Time `json:"createDate"`
- UpdateDate Time `json:"updateData"`
-}
-
-// GearStats describes the stats of gear
-type GearStats struct {
- TotalDistance float64 `json:"totalDistance"`
- TotalActivities int `json:"totalActivities"`
- Processsing bool `json:"processing"`
-}
-
-// Gear will retrieve the details of the users gear
-func (c *Client) Gear(profileID int64) ([]Gear, error) {
- if profileID == 0 && c.Profile == nil {
- return nil, ErrNotAuthenticated
- }
-
- if profileID == 0 && c.Profile != nil {
- profileID = c.Profile.ProfileID
- }
-
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/gear-service/gear/filterGear?userProfilePk=%d",
- profileID,
- )
- var gear []Gear
- err := c.getJSON(URL, &gear)
- if err != nil {
- return nil, err
- }
-
- return gear, nil
-}
-
-// GearType will list the gear types
-func (c *Client) GearType() ([]GearType, error) {
- URL := "https://connect.garmin.com/modern/proxy/gear-service/gear/types"
- var gearType []GearType
- err := c.getJSON(URL, &gearType)
- if err != nil {
- return nil, err
- }
-
- return gearType, nil
-}
-
-// GearStats will get the statistics of an item of gear, given the uuid
-func (c *Client) GearStats(uuid string) (*GearStats, error) {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/userstats-service/gears/%s",
- uuid,
- )
- gearStats := new(GearStats)
- err := c.getJSON(URL, &gearStats)
- if err != nil {
- return nil, err
- }
-
- return gearStats, nil
-}
-
-// GearLink will link an item of gear to an activity. Multiple items of gear can be linked.
-func (c *Client) GearLink(uuid string, activityID int) error {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/gear-service/gear/link/%s/activity/%d",
- uuid,
- activityID,
- )
-
- return c.write("PUT", URL, "", 200)
-}
-
-// GearUnlink will remove an item of gear from an activity. All items of gear can be unlinked.
-func (c *Client) GearUnlink(uuid string, activityID int) error {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/gear-service/gear/unlink/%s/activity/%d",
- uuid,
- activityID,
- )
-
- return c.write("PUT", URL, "", 200)
-}
-
-// GearForActivity will retrieve the gear associated with an activity
-func (c *Client) GearForActivity(profileID int64, activityID int) ([]Gear, error) {
- if profileID == 0 && c.Profile == nil {
- return nil, ErrNotAuthenticated
- }
-
- if profileID == 0 && c.Profile != nil {
- profileID = c.Profile.ProfileID
- }
-
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/gear-service/gear/filterGear?userProfilePk=%d&activityId=%d",
- profileID, activityID,
- )
- var gear []Gear
- err := c.getJSON(URL, &gear)
- if err != nil {
- return nil, err
- }
-
- return gear, nil
-}
diff --git a/python-garmin-connect/Goal.go b/python-garmin-connect/Goal.go
deleted file mode 100644
index 76d7961..0000000
--- a/python-garmin-connect/Goal.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package connect
-
-import (
- "fmt"
-)
-
-// Goal represents a fitness or health goal.
-type Goal struct {
- ID int64 `json:"id"`
- ProfileID int64 `json:"userProfilePK"`
- GoalCategory int `json:"userGoalCategoryPK"`
- GoalType GoalType `json:"userGoalTypePK"`
- Start Date `json:"startDate"`
- End Date `json:"endDate,omitempty"`
- Value int `json:"goalValue"`
- Created Date `json:"createDate"`
-}
-
-// GoalType represents different types of goals.
-type GoalType int
-
-// String implements Stringer.
-func (t GoalType) String() string {
- switch t {
- case 0:
- return "steps-per-day"
- case 4:
- return "weight"
- case 7:
- return "floors-ascended"
- default:
- return fmt.Sprintf("unknown:%d", t)
- }
-}
-
-// Goals lists all goals for displayName of type goalType. If displayName is
-// empty, the currently authenticated user will be used.
-func (c *Client) Goals(displayName string, goalType int) ([]Goal, error) {
- if displayName == "" && c.Profile == nil {
- return nil, ErrNotAuthenticated
- }
-
- if displayName == "" && c.Profile != nil {
- displayName = c.Profile.DisplayName
- }
-
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/wellness-service/wellness/wellness-goals/%s?userGoalType=%d",
- displayName,
- goalType,
- )
-
- goals := make([]Goal, 0, 20)
-
- err := c.getJSON(URL, &goals)
- if err != nil {
- return nil, err
- }
-
- return goals, nil
-}
-
-// AddGoal will add a new goal. If displayName is empty, the currently
-// authenticated user will be used.
-func (c *Client) AddGoal(displayName string, goal Goal) error {
- if displayName == "" && c.Profile == nil {
- return ErrNotAuthenticated
- }
-
- if displayName == "" && c.Profile != nil {
- displayName = c.Profile.DisplayName
- }
-
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/wellness-service/wellness/wellness-goals/%s",
- displayName,
- )
-
- return c.write("POST", URL, goal, 204)
-}
-
-// DeleteGoal will delete an existing goal. If displayName is empty, the
-// currently authenticated user will be used.
-func (c *Client) DeleteGoal(displayName string, goalID int) error {
- if displayName == "" && c.Profile == nil {
- return ErrNotAuthenticated
- }
-
- if displayName == "" && c.Profile != nil {
- displayName = c.Profile.DisplayName
- }
-
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/wellness-service/wellness/wellness-goals/%d/%s",
- goalID,
- displayName,
- )
-
- return c.write("DELETE", URL, nil, 204)
-}
-
-// UpdateGoal will update an existing goal.
-func (c *Client) UpdateGoal(displayName string, goal Goal) error {
- if displayName == "" && c.Profile == nil {
- return ErrNotAuthenticated
- }
-
- if displayName == "" && c.Profile != nil {
- displayName = c.Profile.DisplayName
- }
-
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/wellness-service/wellness/wellness-goals/%d/%s",
- goal.ID,
- displayName,
- )
-
- return c.write("PUT", URL, goal, 204)
-}
diff --git a/python-garmin-connect/Group.go b/python-garmin-connect/Group.go
deleted file mode 100644
index 0dcd899..0000000
--- a/python-garmin-connect/Group.go
+++ /dev/null
@@ -1,153 +0,0 @@
-package connect
-
-import (
- "encoding/json"
- "fmt"
- "net/url"
- "strings"
-)
-
-// Group describes a Garmin Connect group.
-type Group struct {
- ID int `json:"id"`
- Name string `json:"groupName"`
- Description string `json:"groupDescription"`
- OwnerID int `json:"ownerId"`
- ProfileImageURLLarge string `json:"profileImageUrlLarge"`
- ProfileImageURLMedium string `json:"profileImageUrlMedium"`
- ProfileImageURLSmall string `json:"profileImageUrlSmall"`
- Visibility string `json:"groupVisibility"`
- Privacy string `json:"groupPrivacy"`
- Location string `json:"location"`
- WebsiteURL string `json:"websiteUrl"`
- FacebookURL string `json:"facebookUrl"`
- TwitterURL string `json:"twitterUrl"`
- PrimaryActivities []string `json:"primaryActivities"`
- OtherPrimaryActivity string `json:"otherPrimaryActivity"`
- LeaderboardTypes []string `json:"leaderboardTypes"`
- FeatureTypes []string `json:"featureTypes"`
- CorporateWellness bool `json:"isCorporateWellness"`
- ActivityFeedTypes []ActivityType `json:"activityFeedTypes"`
-}
-
-/*
-Unknowns:
-"membershipStatus": null,
-"isCorporateWellness": false,
-"programName": null,
-"programTextColor": null,
-"programBackgroundColor": null,
-"groupMemberCount": null,
-*/
-
-// Groups will return the group membership. If displayName is empty, the
-// currently authenticated user will be used.
-func (c *Client) Groups(displayName string) ([]Group, error) {
- if displayName == "" && c.Profile == nil {
- return nil, ErrNotAuthenticated
- }
-
- if displayName == "" && c.Profile != nil {
- displayName = c.Profile.DisplayName
- }
-
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/group-service/groups/%s", displayName)
-
- groups := make([]Group, 0, 30)
-
- err := c.getJSON(URL, &groups)
- if err != nil {
- return nil, err
- }
-
- return groups, nil
-}
-
-// SearchGroups can search for groups in Garmin Connect.
-func (c *Client) SearchGroups(keyword string) ([]Group, error) {
- URL := "https://connect.garmin.com/modern/proxy/group-service/keyword"
-
- payload := url.Values{
- "start": {"1"},
- "limit": {"100"},
- "keyword": {keyword},
- }
-
- req, err := c.newRequest("POST", URL, strings.NewReader(payload.Encode()))
- if err != nil {
- return nil, err
- }
-
- req.Header.Add("content-type", "application/x-www-form-urlencoded; charset=UTF-8")
-
- resp, err := c.do(req)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
-
- var proxy struct {
- Groups []Group `json:"groupDTOs"`
- }
-
- dec := json.NewDecoder(resp.Body)
- err = dec.Decode(&proxy)
- if err != nil {
- return nil, err
- }
-
- return proxy.Groups, nil
-}
-
-// Group returns details about groupID.
-func (c *Client) Group(groupID int) (*Group, error) {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/group-service/group/%d", groupID)
-
- group := new(Group)
-
- err := c.getJSON(URL, group)
- if err != nil {
- return nil, err
- }
-
- return group, nil
-}
-
-// JoinGroup joins a group. If profileID is 0, the currently authenticated
-// user will be used.
-func (c *Client) JoinGroup(groupID int) error {
- if c.Profile == nil {
- return ErrNotAuthenticated
- }
-
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/group-service/group/%d/member/%d",
- groupID,
- c.Profile.ProfileID,
- )
-
- payload := struct {
- GroupID int `json:"groupId"`
- Role *string `json:"groupRole"` // is always null?
- ProfileID int64 `json:"userProfileId"`
- }{
- groupID,
- nil,
- c.Profile.ProfileID,
- }
-
- return c.write("POST", URL, payload, 200)
-}
-
-// LeaveGroup leaves a group.
-func (c *Client) LeaveGroup(groupID int) error {
- if c.Profile == nil {
- return ErrNotAuthenticated
- }
-
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/group-service/group/%d/member/%d",
- groupID,
- c.Profile.ProfileID,
- )
-
- return c.write("DELETE", URL, nil, 204)
-}
diff --git a/python-garmin-connect/GroupAnnouncement.go b/python-garmin-connect/GroupAnnouncement.go
deleted file mode 100644
index d15590c..0000000
--- a/python-garmin-connect/GroupAnnouncement.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package connect
-
-import (
- "fmt"
-)
-
-// GroupAnnouncement describes a group announcement. Only one announcement can
-// exist per group.
-type GroupAnnouncement struct {
- ID int `json:"announcementId"`
- GroupID int `json:"groupId"`
- Title string `json:"title"`
- Message string `json:"message"`
- ExpireDate Time `json:"expireDate"`
- AnnouncementDate Time `json:"announcementDate"`
-}
-
-// GroupAnnouncement returns the announcement for groupID.
-func (c *Client) GroupAnnouncement(groupID int) (*GroupAnnouncement, error) {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/group-service/group/%d/announcement",
- groupID,
- )
-
- announcement := new(GroupAnnouncement)
- err := c.getJSON(URL, announcement)
- if err != nil {
- return nil, err
- }
-
- return announcement, nil
-}
diff --git a/python-garmin-connect/GroupMember.go b/python-garmin-connect/GroupMember.go
deleted file mode 100644
index d9a56e8..0000000
--- a/python-garmin-connect/GroupMember.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package connect
-
-import (
- "fmt"
- "time"
-)
-
-// GroupMember describes a member of a group.
-type GroupMember struct {
- SocialProfile
-
- Joined time.Time `json:"joinDate"`
- Role string `json:"groupRole"`
-}
-
-// GroupMembers will return the member list of a group.
-func (c *Client) GroupMembers(groupID int) ([]GroupMember, error) {
- type proxy struct {
- ID string `json:"id"`
- GroupID int `json:"groupId"`
- UserProfileID int64 `json:"userProfileId"`
- DisplayName string `json:"displayName"`
- Location string `json:"location"`
- Joined Date `json:"joinDate"`
- Role string `json:"groupRole"`
- Name string `json:"fullName"`
- ProfileImageURLLarge string `json:"profileImageLarge"`
- ProfileImageURLMedium string `json:"profileImageMedium"`
- ProfileImageURLSmall string `json:"profileImageSmall"`
- Pro bool `json:"userPro"`
- Level int `json:"userLevel"`
- }
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/group-service/group/%d/members",
- groupID,
- )
-
- membersProxy := make([]proxy, 0, 100)
- err := c.getJSON(URL, &membersProxy)
- if err != nil {
- return nil, err
- }
-
- members := make([]GroupMember, len(membersProxy))
- for i, p := range membersProxy {
- members[i].DisplayName = p.DisplayName
- members[i].ProfileID = p.UserProfileID
- members[i].DisplayName = p.DisplayName
- members[i].Location = p.Location
- members[i].Fullname = p.Name
- members[i].ProfileImageURLLarge = p.ProfileImageURLLarge
- members[i].ProfileImageURLMedium = p.ProfileImageURLMedium
- members[i].ProfileImageURLSmall = p.ProfileImageURLSmall
- members[i].UserLevel = p.Level
-
- members[i].Joined = p.Joined.Time()
- members[i].Role = p.Role
- }
-
- return members, nil
-}
diff --git a/python-garmin-connect/LICENSE b/python-garmin-connect/LICENSE
deleted file mode 100644
index a07c1c7..0000000
--- a/python-garmin-connect/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2021 Anders Brander
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/python-garmin-connect/LastUsed.go b/python-garmin-connect/LastUsed.go
deleted file mode 100644
index 81a5bb8..0000000
--- a/python-garmin-connect/LastUsed.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package connect
-
-// LastUsed describes the last synchronization.
-type LastUsed struct {
- DeviceID int `json:"userDeviceId"`
- ProfileNumber int `json:"userProfileNumber"`
- ApplicationNumber int `json:"applicationNumber"`
- DeviceApplicationKey string `json:"lastUsedDeviceApplicationKey"`
- DeviceName string `json:"lastUsedDeviceName"`
- DeviceUploadTime Time `json:"lastUsedDeviceUploadTime"`
- ImageURL string `json:"imageUrl"`
- Released bool `json:"released"`
-}
-
-// LastUsed will return information about the latest synchronization.
-func (c *Client) LastUsed(displayName string) (*LastUsed, error) {
- URL := "https://connect.garmin.com/modern/proxy/device-service/deviceservice/userlastused/" + displayName
-
- lastused := new(LastUsed)
-
- err := c.getJSON(URL, lastused)
- if err != nil {
- return nil, err
- }
-
- return lastused, err
-}
diff --git a/python-garmin-connect/LifetimeActivities.go b/python-garmin-connect/LifetimeActivities.go
deleted file mode 100644
index 933753e..0000000
--- a/python-garmin-connect/LifetimeActivities.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package connect
-
-import (
- "errors"
-)
-
-// LifetimeActivities is describing a basic summary of all activities.
-type LifetimeActivities struct {
- Activities int `json:"totalActivities"` // The number of activities
- Distance float64 `json:"totalDistance"` // The total distance in meters
- Duration float64 `json:"totalDuration"` // The duration of all activities in seconds
- Calories float64 `json:"totalCalories"` // Energy in C
- ElevationGain float64 `json:"totalElevationGain"` // Total elevation gain in meters
-}
-
-// LifetimeActivities will return some aggregated data about all activities.
-func (c *Client) LifetimeActivities(displayName string) (*LifetimeActivities, error) {
- URL := "https://connect.garmin.com/modern/proxy/userstats-service/statistics/" + displayName
-
- var proxy struct {
- Activities []LifetimeActivities `json:"userMetrics"`
- }
-
- err := c.getJSON(URL, &proxy)
- if err != nil {
- return nil, err
- }
-
- if len(proxy.Activities) != 1 {
- return nil, errors.New("unexpected data")
- }
-
- return &proxy.Activities[0], err
-}
diff --git a/python-garmin-connect/LifetimeTotals.go b/python-garmin-connect/LifetimeTotals.go
deleted file mode 100644
index e8dba23..0000000
--- a/python-garmin-connect/LifetimeTotals.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package connect
-
-// LifetimeTotals is ligetime statistics for the Connect user.
-type LifetimeTotals struct {
- ProfileID int `json:"userProfileId"`
- ActiveDays int `json:"totalActiveDays"`
- Calories float64 `json:"totalCalories"`
- Distance int `json:"totalDistance"`
- GoalsMetInDays int `json:"totalGoalsMetInDays"`
- Steps int `json:"totalSteps"`
-}
-
-// LifetimeTotals returns some lifetime statistics for displayName.
-func (c *Client) LifetimeTotals(displayName string) (*LifetimeTotals, error) {
- URL := "https://connect.garmin.com/modern/proxy/usersummary-service/stats/connectLifetimeTotals/" + displayName
-
- totals := new(LifetimeTotals)
-
- err := c.getJSON(URL, totals)
- if err != nil {
- return nil, err
- }
-
- return totals, err
-}
diff --git a/python-garmin-connect/Logger.go b/python-garmin-connect/Logger.go
deleted file mode 100644
index 7e8dde4..0000000
--- a/python-garmin-connect/Logger.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package connect
-
-// Logger defines the interface understood by the Connect client for logging.
-type Logger interface {
- Printf(format string, v ...interface{})
-}
-
-type discardLog struct{}
-
-func (*discardLog) Printf(format string, v ...interface{}) {
-}
diff --git a/python-garmin-connect/PersonalInformation.go b/python-garmin-connect/PersonalInformation.go
deleted file mode 100644
index d7e6d89..0000000
--- a/python-garmin-connect/PersonalInformation.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package connect
-
-// BiometricProfile holds key biometric data.
-type BiometricProfile struct {
- UserID int `json:"userId"`
- Height float64 `json:"height"`
- Weight float64 `json:"weight"` // grams
- VO2Max float64 `json:"vo2Max"`
- VO2MaxCycling float64 `json:"vo2MaxCycling"`
-}
-
-// UserInfo is very basic information about a user.
-type UserInfo struct {
- Gender string `json:"genderType"`
- Email string `json:"email"`
- Locale string `json:"locale"`
- TimeZone string `json:"timezone"`
- Age int `json:"age"`
-}
-
-// PersonalInformation is user info and a biometric profile for a user.
-type PersonalInformation struct {
- UserInfo UserInfo `json:"userInfo"`
- BiometricProfile BiometricProfile `json:"biometricProfile"`
-}
-
-// PersonalInformation will retrieve personal information for displayName.
-func (c *Client) PersonalInformation(displayName string) (*PersonalInformation, error) {
- URL := "https://connect.garmin.com/modern/proxy/userprofile-service/userprofile/personal-information/" + displayName
-
- pi := new(PersonalInformation)
-
- err := c.getJSON(URL, pi)
- if err != nil {
- return nil, err
- }
-
- return pi, nil
-}
diff --git a/python-garmin-connect/README.md b/python-garmin-connect/README.md
deleted file mode 100644
index b9a14c4..0000000
--- a/python-garmin-connect/README.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# garmin-connect
-
-Golang client for the Garmin Connect API.
-
-This is nothing but a proof of concept, and the API may change at any time.
-
-[![GoDoc][1]][2]
-
-[1]: https://godoc.org/github.com/abrander/garmin-connect?status.svg
-[2]: https://godoc.org/github.com/abrander/garmin-connect
-
-# Install
-
-The `connect` CLI app can be installed using `go install`, and the package using `go get`.
-
-```
-go install github.com/abrander/garmin-connect/connect@latest
-```
-
-```
-go get github.com/abrander/garmin-connect@latest
-```
diff --git a/python-garmin-connect/SleepState.go b/python-garmin-connect/SleepState.go
deleted file mode 100644
index 9b4bb64..0000000
--- a/python-garmin-connect/SleepState.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package connect
-
-// SleepState is used to describe the state of sleep with a device capable
-// of measuring sleep health.
-type SleepState int
-
-// Known sleep states in Garmin Connect.
-const (
- SleepStateUnknown SleepState = -1
- SleepStateDeep SleepState = 0
- SleepStateLight SleepState = 1
- SleepStateREM SleepState = 2
- SleepStateAwake SleepState = 3
-)
-
-// UnmarshalJSON implements json.Unmarshaler.
-func (s *SleepState) UnmarshalJSON(value []byte) error {
- // Garmin abuses floats to transfers enums. We ignore the value, and
- // simply compares them as strings.
- switch string(value) {
- case "0.0":
- *s = SleepStateDeep
- case "1.0":
- *s = SleepStateLight
- case "2.0":
- *s = SleepStateREM
- case "3.0":
- *s = SleepStateAwake
- default:
- *s = SleepStateUnknown
- }
-
- return nil
-}
-
-// Sleep implements fmt.Stringer.
-func (s SleepState) String() string {
- m := map[SleepState]string{
- SleepStateUnknown: "Unknown",
- SleepStateDeep: "Deep",
- SleepStateLight: "Light",
- SleepStateREM: "REM",
- SleepStateAwake: "Awake",
- }
-
- str, found := m[s]
- if !found {
- str = m[SleepStateUnknown]
- }
-
- return str
-}
diff --git a/python-garmin-connect/SleepSummary.go b/python-garmin-connect/SleepSummary.go
deleted file mode 100644
index 49d0317..0000000
--- a/python-garmin-connect/SleepSummary.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package connect
-
-import (
- "fmt"
- "time"
-)
-
-// "sleepQualityTypePK": null,
-// "sleepResultTypePK": null,
-
-// SleepSummary is a summary of sleep for a single night.
-type SleepSummary struct {
- ID int64 `json:"id"`
- UserProfilePK int64 `json:"userProfilePK"`
- Sleep time.Duration `json:"sleepTimeSeconds"`
- Nap time.Duration `json:"napTimeSeconds"`
- Confirmed bool `json:"sleepWindowConfirmed"`
- Confirmation string `json:"sleepWindowConfirmationType"`
- StartGMT Time `json:"sleepStartTimestampGMT"`
- EndGMT Time `json:"sleepEndTimestampGMT"`
- StartLocal Time `json:"sleepStartTimestampLocal"`
- EndLocal Time `json:"sleepEndTimestampLocal"`
- AutoStartGMT Time `json:"autoSleepStartTimestampGMT"`
- AutoEndGMT Time `json:"autoSleepEndTimestampGMT"`
- Unmeasurable time.Duration `json:"unmeasurableSleepSeconds"`
- Deep time.Duration `json:"deepSleepSeconds"`
- Light time.Duration `json:"lightSleepSeconds"`
- REM time.Duration `json:"remSleepSeconds"`
- Awake time.Duration `json:"awakeSleepSeconds"`
- DeviceRemCapable bool `json:"deviceRemCapable"`
- REMData bool `json:"remData"`
-}
-
-// SleepMovement denotes the amount of movement for a short time period
-// during sleep.
-type SleepMovement struct {
- Start Time `json:"startGMT"`
- End Time `json:"endGMT"`
- Level float64 `json:"activityLevel"`
-}
-
-// SleepLevel represents the sleep level for a longer period of time.
-type SleepLevel struct {
- Start Time `json:"startGMT"`
- End Time `json:"endGMT"`
- State SleepState `json:"activityLevel"`
-}
-
-// SleepData will retrieve sleep data for date for a given displayName. If
-// displayName is empty, the currently authenticated user will be used.
-func (c *Client) SleepData(displayName string, date time.Time) (*SleepSummary, []SleepMovement, []SleepLevel, error) {
- if displayName == "" && c.Profile == nil {
- return nil, nil, nil, ErrNotAuthenticated
- }
-
- if displayName == "" && c.Profile != nil {
- displayName = c.Profile.DisplayName
- }
-
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/wellness-service/wellness/dailySleepData/%s?date=%s&nonSleepBufferMinutes=60",
- displayName,
- formatDate(date),
- )
-
- var proxy struct {
- SleepSummary SleepSummary `json:"dailySleepDTO"`
- REMData bool `json:"remSleepData"`
- Movement []SleepMovement `json:"sleepMovement"`
- Levels []SleepLevel `json:"sleepLevels"`
- }
-
- err := c.getJSON(URL, &proxy)
- if err != nil {
- return nil, nil, nil, err
- }
-
- // All timings from Garmin are in seconds.
- proxy.SleepSummary.Sleep *= time.Second
- proxy.SleepSummary.Nap *= time.Second
- proxy.SleepSummary.Unmeasurable *= time.Second
- proxy.SleepSummary.Deep *= time.Second
- proxy.SleepSummary.Light *= time.Second
- proxy.SleepSummary.REM *= time.Second
- proxy.SleepSummary.Awake *= time.Second
-
- proxy.SleepSummary.REMData = proxy.REMData
-
- return &proxy.SleepSummary, proxy.Movement, proxy.Levels, nil
-}
diff --git a/python-garmin-connect/SocialProfile.go b/python-garmin-connect/SocialProfile.go
deleted file mode 100644
index 35afed6..0000000
--- a/python-garmin-connect/SocialProfile.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package connect
-
-// SocialProfile represents a Garmin Connect user.
-type SocialProfile struct {
- ID int64 `json:"id"`
- ProfileID int64 `json:"profileId"`
- ConnectionRequestID int `json:"connectionRequestId"`
- GarminGUID string `json:"garminGUID"`
- DisplayName string `json:"displayName"`
- Fullname string `json:"fullName"`
- Username string `json:"userName"`
- ProfileImageURLLarge string `json:"profileImageUrlLarge"`
- ProfileImageURLMedium string `json:"profileImageUrlMedium"`
- ProfileImageURLSmall string `json:"profileImageUrlSmall"`
- Location string `json:"location"`
- FavoriteActivityTypes []string `json:"favoriteActivityTypes"`
- UserRoles []string `json:"userRoles"`
- UserProfileFullName string `json:"userProfileFullName"`
- UserLevel int `json:"userLevel"`
- UserPoint int `json:"userPoint"`
-}
-
-// SocialProfile retrieves a profile for a Garmin Connect user. If displayName
-// is empty, the profile for the currently authenticated user will be returned.
-func (c *Client) SocialProfile(displayName string) (*SocialProfile, error) {
- URL := "https://connect.garmin.com/modern/proxy/userprofile-service/socialProfile/" + displayName
-
- profile := new(SocialProfile)
-
- err := c.getJSON(URL, profile)
- if err != nil {
- return nil, err
- }
-
- return profile, err
-}
-
-// PublicSocialProfile retrieves the public profile for displayName.
-func (c *Client) PublicSocialProfile(displayName string) (*SocialProfile, error) {
- URL := "https://connect.garmin.com/modern/proxy/userprofile-service/socialProfile/public/" + displayName
-
- profile := new(SocialProfile)
-
- err := c.getJSON(URL, profile)
- if err != nil {
- return nil, err
- }
-
- return profile, err
-}
-
-// BlockedUsers returns the list of blocked users for the currently
-// authenticated user.
-func (c *Client) BlockedUsers() ([]SocialProfile, error) {
- URL := "https://connect.garmin.com/modern/proxy/userblock-service/blockuser"
-
- var results []SocialProfile
-
- err := c.getJSON(URL, &results)
- if err != nil {
- return nil, err
- }
-
- return results, nil
-}
-
-// BlockUser will block a user.
-func (c *Client) BlockUser(displayName string) error {
- URL := "https://connect.garmin.com/modern/proxy/userblock-service/blockuser/" + displayName
-
- return c.write("POST", URL, nil, 200)
-}
-
-// UnblockUser removed displayName from the block list.
-func (c *Client) UnblockUser(displayName string) error {
- URL := "https://connect.garmin.com/modern/proxy/userblock-service/blockuser/" + displayName
-
- return c.write("DELETE", URL, nil, 204)
-}
diff --git a/python-garmin-connect/Time.go b/python-garmin-connect/Time.go
deleted file mode 100644
index 709d7a4..0000000
--- a/python-garmin-connect/Time.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package connect
-
-import (
- "encoding/json"
- "strconv"
- "time"
-)
-
-// Time is a type masking a time.Time capable of parsing the JSON from
-// Garmin Connect.
-type Time struct{ time.Time }
-
-// UnmarshalJSON implements json.Unmarshaler. It can parse timestamps
-// returned from connect.garmin.com.
-func (t *Time) UnmarshalJSON(value []byte) error {
- // Sometimes timestamps are transferred as milliseconds since epoch :-/
- i, err := strconv.ParseInt(string(value), 10, 64)
- if err == nil && i > 1000000000000 {
- t.Time = time.Unix(i/1000, 0)
-
- return nil
- }
-
- // FIXME: Somehow we should deal with timezones :-/
- layouts := []string{
- "2006-01-02T15:04:05Z", // Support Gos own format.
- "2006-01-02T15:04:05.0",
- "2006-01-02 15:04:05",
- }
-
- var blip string
- err = json.Unmarshal(value, &blip)
- if err != nil {
- return err
- }
-
- var proxy time.Time
- for _, l := range layouts {
- proxy, err = time.Parse(l, blip)
- if err == nil {
- break
- }
- }
-
- t.Time = proxy
-
- return nil
-}
-
-// MarshalJSON implements json.Marshaler.
-func (t *Time) MarshalJSON() ([]byte, error) {
- b, err := t.Time.MarshalJSON()
-
- return b, err
-}
diff --git a/python-garmin-connect/Time_test.go b/python-garmin-connect/Time_test.go
deleted file mode 100644
index 73954df..0000000
--- a/python-garmin-connect/Time_test.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package connect
-
-import (
- "encoding/json"
- "testing"
-)
-
-func TestTimeUnmarshalJSON(t *testing.T) {
- var t0 Time
-
- input := []byte(`"2019-01-12T11:45:23.0"`)
-
- err := json.Unmarshal(input, &t0)
- if err != nil {
- t.Fatalf("Error parsing %s: %s", string(input), err.Error())
- }
-
- if t0.String() != "2019-01-12 11:45:23 +0000 UTC" {
- t.Errorf("Failed to parse `%s` correct, got %s", string(input), t0.String())
- }
-}
diff --git a/python-garmin-connect/Timezone.go b/python-garmin-connect/Timezone.go
deleted file mode 100644
index c51871a..0000000
--- a/python-garmin-connect/Timezone.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package connect
-
-import (
- "time"
-)
-
-// Timezone represents a timezone in Garmin Connect.
-type Timezone struct {
- ID int `json:"unitId"`
- Key string `json:"unitKey"`
- GMTOffset float64 `json:"gmtOffset"`
- DSTOffset float64 `json:"dstOffset"`
- Group int `json:"groupNumber"`
- TimeZone string `json:"timeZone"`
-}
-
-// Location will (try to) return a location for use with time.Time functions.
-func (t *Timezone) Location() (*time.Location, error) {
- return time.LoadLocation(t.Key)
-}
diff --git a/python-garmin-connect/Timezones.go b/python-garmin-connect/Timezones.go
deleted file mode 100644
index 1fbde8c..0000000
--- a/python-garmin-connect/Timezones.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package connect
-
-// Timezones is the list of known time zones in Garmin Connect.
-type Timezones []Timezone
-
-// Timezones will retrieve the list of known timezones in Garmin Connect.
-func (c *Client) Timezones() (Timezones, error) {
- URL := "https://connect.garmin.com/modern/proxy/system-service/timezoneUnits"
-
- if !c.authenticated() {
- return nil, ErrNotAuthenticated
- }
-
- timezones := make(Timezones, 0, 100)
-
- err := c.getJSON(URL, &timezones)
- if err != nil {
- return nil, err
- }
-
- return timezones, nil
-}
-
-// FindID will search for the timezone with id.
-func (ts Timezones) FindID(id int) (Timezone, bool) {
- for _, t := range ts {
- if t.ID == id {
- return t, true
- }
- }
-
- return Timezone{}, false
-}
-
-// FindKey will search for the timezone with key key.
-func (ts Timezones) FindKey(key string) (Timezone, bool) {
- for _, t := range ts {
- if t.Key == key {
- return t, true
- }
- }
-
- return Timezone{}, false
-}
diff --git a/python-garmin-connect/Weight.go b/python-garmin-connect/Weight.go
deleted file mode 100644
index 9391e80..0000000
--- a/python-garmin-connect/Weight.go
+++ /dev/null
@@ -1,167 +0,0 @@
-package connect
-
-import (
- "fmt"
- "time"
-)
-
-// Weightin is a single weight event.
-type Weightin struct {
- Date Date `json:"date"`
- Version int `json:"version"`
- Weight float64 `json:"weight"` // gram
- BMI float64 `json:"bmi"` // weight / height²
- BodyFatPercentage float64 `json:"bodyFat"` // percent
- BodyWater float64 `json:"bodyWater"` // kilogram
- BoneMass int `json:"boneMass"` // gram
- MuscleMass int `json:"muscleMass"` // gram
- SourceType string `json:"sourceType"`
-}
-
-// WeightAverage is aggregated weight data for a specific period.
-type WeightAverage struct {
- Weightin
- From int `json:"from"`
- Until int `json:"until"`
-}
-
-// LatestWeight will retrieve the latest weight by date.
-func (c *Client) LatestWeight(date time.Time) (*Weightin, error) {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/weight-service/weight/latest?date=%04d-%02d-%02d",
- date.Year(),
- date.Month(),
- date.Day())
-
- wi := new(Weightin)
-
- err := c.getJSON(URL, wi)
- if err != nil {
- return nil, err
- }
-
- return wi, nil
-}
-
-// Weightins will retrieve all weight ins between startDate and endDate. A
-// summary is provided as well. This summary is calculated by Garmin Connect.
-func (c *Client) Weightins(startDate time.Time, endDate time.Time) (*WeightAverage, []Weightin, error) {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/weight-service/weight/dateRange?startDate=%s&endDate=%s",
- formatDate(startDate),
- formatDate(endDate))
-
- // An alternative endpoint for weight info this can be found here:
- // https://connect.garmin.com/modern/proxy/userprofile-service/userprofile/personal-information/weightWithOutbound?from=1556359100000&until=1556611800000
-
- if !c.authenticated() {
- return nil, nil, ErrNotAuthenticated
- }
-
- var proxy struct {
- DateWeightList []Weightin `json:"dateWeightList"`
- TotalAverage *WeightAverage `json:"totalAverage"`
- }
-
- err := c.getJSON(URL, &proxy)
- if err != nil {
- return nil, nil, err
- }
-
- return proxy.TotalAverage, proxy.DateWeightList, nil
-}
-
-// DeleteWeightin will delete all biometric data for date.
-func (c *Client) DeleteWeightin(date time.Time) error {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/biometric-service/biometric/%s", formatDate(date))
-
- if !c.authenticated() {
- return ErrNotAuthenticated
- }
-
- return c.write("DELETE", URL, nil, 204)
-}
-
-// AddUserWeight will add a manual weight in. weight is in grams to match
-// Weightin.
-func (c *Client) AddUserWeight(date time.Time, weight float64) error {
- URL := "https://connect.garmin.com/modern/proxy/weight-service/user-weight"
- payload := struct {
- Date string `json:"date"`
- UnitKey string `json:"unitKey"`
- Value float64 `json:"value"`
- }{
- Date: formatDate(date),
- UnitKey: "kg",
- Value: weight / 1000.0,
- }
-
- return c.write("POST", URL, payload, 204)
-}
-
-// WeightByDate retrieves the weight of date if available. If no weight data
-// for date exists, it will return ErrNotFound.
-func (c *Client) WeightByDate(date time.Time) (Time, float64, error) {
- URL := fmt.Sprintf("https://connect.garmin.com/modern/proxy/biometric-service/biometric/weightByDate?date=%s",
- formatDate(date))
-
- if !c.authenticated() {
- return Time{}, 0.0, ErrNotAuthenticated
- }
-
- var proxy []struct {
- TimeStamp Time `json:"weightDate"`
- Weight float64 `json:"weight"` // gram
- }
-
- err := c.getJSON(URL, &proxy)
- if err != nil {
- return Time{}, 0.0, err
- }
-
- if len(proxy) < 1 {
- return Time{}, 0.0, ErrNotFound
- }
-
- return proxy[0].TimeStamp, proxy[0].Weight, nil
-}
-
-// WeightGoal will list the users weight goal if any. If displayName is empty,
-// the currently authenticated user will be used.
-func (c *Client) WeightGoal(displayName string) (*Goal, error) {
- goals, err := c.Goals(displayName, 4)
- if err != nil {
- return nil, err
- }
-
- if len(goals) < 1 {
- return nil, ErrNotFound
- }
-
- return &goals[0], nil
-}
-
-// SetWeightGoal will set a new weight goal.
-func (c *Client) SetWeightGoal(goal int) error {
- if !c.authenticated() || c.Profile == nil {
- return ErrNotAuthenticated
- }
-
- g := Goal{
- Created: Today(),
- Start: Today(),
- GoalType: 4,
- ProfileID: c.Profile.ProfileID,
- Value: goal,
- }
-
- goals, err := c.Goals("", 4)
- if err != nil {
- return err
- }
-
- if len(goals) >= 1 {
- g.ID = goals[0].ID
- return c.UpdateGoal("", g)
- }
-
- return c.AddGoal(c.Profile.DisplayName, g)
-}
diff --git a/python-garmin-connect/connect/.gitignore b/python-garmin-connect/connect/.gitignore
deleted file mode 100644
index 6b9fe89..0000000
--- a/python-garmin-connect/connect/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/connect
diff --git a/python-garmin-connect/connect/README.md b/python-garmin-connect/connect/README.md
deleted file mode 100644
index 4b3e0f5..0000000
--- a/python-garmin-connect/connect/README.md
+++ /dev/null
@@ -1 +0,0 @@
-This is a simple CLI client for Garmin Connect.
diff --git a/python-garmin-connect/connect/Table.go b/python-garmin-connect/connect/Table.go
deleted file mode 100644
index 112ae7f..0000000
--- a/python-garmin-connect/connect/Table.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package main
-
-import (
- "fmt"
- "io"
- "unicode/utf8"
-)
-
-type Table struct {
- columnsMax []int
- header []string
- rows [][]string
-}
-
-func NewTable() *Table {
- return &Table{}
-}
-
-func (t *Table) AddHeader(titles ...string) {
- t.header = titles
- t.columnsMax = make([]int, len(t.header))
- for i, title := range t.header {
- t.columnsMax[i] = utf8.RuneCountInString(title)
- }
-}
-
-func (t *Table) AddRow(columns ...interface{}) {
- cols := sliceStringer(columns)
-
- if len(columns) != len(t.header) {
- panic("worng number of columns")
- }
-
- t.rows = append(t.rows, cols)
-
- for i, col := range cols {
- l := utf8.RuneCountInString(col)
-
- if t.columnsMax[i] < l {
- t.columnsMax[i] = l
- }
- }
-}
-
-func rightPad(in string, length int) string {
- result := in
- inLen := utf8.RuneCountInString(in)
-
- for i := 0; i < length-inLen; i++ {
- result += " "
- }
-
- return result
-}
-
-func (t *Table) outputLine(w io.Writer, columns []string) {
- line := ""
-
- for i, column := range columns {
- line += rightPad(column, t.columnsMax[i]) + " "
- }
-
- fmt.Fprintf(w, "%s\n", line)
-}
-
-func (t *Table) outputHeader(w io.Writer, columns []string) {
- line := ""
-
- for i, column := range columns {
- line += "\033[1m" + rightPad(column, t.columnsMax[i]) + "\033[0m "
- }
-
- fmt.Fprintf(w, "%s\n", line)
-}
-
-func (t *Table) Output(writer io.Writer) {
- t.outputHeader(writer, t.header)
- for _, row := range t.rows {
- t.outputLine(writer, row)
- }
-}
diff --git a/python-garmin-connect/connect/Tabular.go b/python-garmin-connect/connect/Tabular.go
deleted file mode 100644
index e8c4213..0000000
--- a/python-garmin-connect/connect/Tabular.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package main
-
-import (
- "fmt"
- "io"
- "unicode/utf8"
-)
-
-type Tabular struct {
- maxLength int
- titles []string
- values []Value
-}
-
-type Value struct {
- Unit string
- Value interface{}
-}
-
-func (v Value) String() string {
- str := stringer(v.Value)
-
- return "\033[1m" + str + "\033[0m " + v.Unit
-}
-
-func NewTabular() *Tabular {
- return &Tabular{}
-}
-
-func (t *Tabular) AddValue(title string, value interface{}) {
- t.AddValueUnit(title, value, "")
-}
-
-func (t *Tabular) AddValueUnit(title string, value interface{}, unit string) {
- v := Value{
- Unit: unit,
- Value: value,
- }
-
- t.titles = append(t.titles, title)
- t.values = append(t.values, v)
-
- if len(title) > t.maxLength {
- t.maxLength = len(title)
- }
-}
-
-func leftPad(in string, length int) string {
- result := ""
- inLen := utf8.RuneCountInString(in)
-
- for i := 0; i < length-inLen; i++ {
- result += " "
- }
-
- return result + in
-}
-
-func (t *Tabular) Output(writer io.Writer) {
- for i, value := range t.values {
- fmt.Fprintf(writer, "%s %s\n", leftPad(t.titles[i], t.maxLength), value.String())
- }
-}
diff --git a/python-garmin-connect/connect/activities.go b/python-garmin-connect/connect/activities.go
deleted file mode 100644
index 0bdf22a..0000000
--- a/python-garmin-connect/connect/activities.go
+++ /dev/null
@@ -1,217 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
- "strconv"
-
- "github.com/spf13/cobra"
-
- connect "github.com/abrander/garmin-connect"
-)
-
-var (
- exportFormat string
- offset int
- count int
-)
-
-func init() {
- activitiesCmd := &cobra.Command{
- Use: "activities",
- }
- rootCmd.AddCommand(activitiesCmd)
-
- activitiesListCmd := &cobra.Command{
- Use: "list [display name]",
- Short: "List Activities",
- Run: activitiesList,
- Args: cobra.RangeArgs(0, 1),
- }
- activitiesListCmd.Flags().IntVarP(&offset, "offset", "o", 0, "Paginating index where the list starts from")
- activitiesListCmd.Flags().IntVarP(&count, "count", "c", 100, "Count of elements to return")
- activitiesCmd.AddCommand(activitiesListCmd)
-
- activitiesViewCmd := &cobra.Command{
- Use: "view ",
- Short: "View details for an activity",
- Run: activitiesView,
- Args: cobra.ExactArgs(1),
- }
- activitiesCmd.AddCommand(activitiesViewCmd)
-
- activitiesViewWeatherCmd := &cobra.Command{
- Use: "weather ",
- Short: "View weather for an activity",
- Run: activitiesViewWeather,
- Args: cobra.ExactArgs(1),
- }
- activitiesViewCmd.AddCommand(activitiesViewWeatherCmd)
-
- activitiesViewHRZonesCmd := &cobra.Command{
- Use: "hrzones ",
- Short: "View hr zones for an activity",
- Run: activitiesViewHRZones,
- Args: cobra.ExactArgs(1),
- }
- activitiesViewCmd.AddCommand(activitiesViewHRZonesCmd)
-
- activitiesExportCmd := &cobra.Command{
- Use: "export ",
- Short: "Export an activity to a file",
- Run: activitiesExport,
- Args: cobra.ExactArgs(1),
- }
- activitiesExportCmd.Flags().StringVarP(&exportFormat, "format", "f", "fit", "Format of export (fit, tcx, gpx, kml, csv)")
- activitiesCmd.AddCommand(activitiesExportCmd)
-
- activitiesImportCmd := &cobra.Command{
- Use: "import ",
- Short: "Import an activity from a file",
- Run: activitiesImport,
- Args: cobra.ExactArgs(1),
- }
- activitiesCmd.AddCommand(activitiesImportCmd)
-
- activitiesDeleteCmd := &cobra.Command{
- Use: "delete ",
- Short: "Delete an activity",
- Run: activitiesDelete,
- Args: cobra.ExactArgs(1),
- }
- activitiesCmd.AddCommand(activitiesDeleteCmd)
-
- activitiesRenameCmd := &cobra.Command{
- Use: "rename ",
- Short: "Rename an activity",
- Run: activitiesRename,
- Args: cobra.ExactArgs(2),
- }
- activitiesCmd.AddCommand(activitiesRenameCmd)
-}
-
-func activitiesList(_ *cobra.Command, args []string) {
- displayName := ""
-
- if len(args) == 1 {
- displayName = args[0]
- }
-
- activities, err := client.Activities(displayName, offset, count)
- bail(err)
-
- t := NewTable()
- t.AddHeader("ID", "Date", "Name", "Type", "Distance", "Time", "Avg/Max HR", "Calories")
- for _, a := range activities {
- t.AddRow(
- a.ID,
- a.StartLocal.Time,
- a.ActivityName,
- a.ActivityType.TypeKey,
- a.Distance,
- a.StartLocal,
- fmt.Sprintf("%.0f/%.0f", a.AverageHeartRate, a.MaxHeartRate),
- a.Calories,
- )
- }
- t.Output(os.Stdout)
-}
-
-func activitiesView(_ *cobra.Command, args []string) {
- activityID, err := strconv.Atoi(args[0])
- bail(err)
-
- activity, err := client.Activity(activityID)
- bail(err)
-
- t := NewTabular()
- t.AddValue("ID", activity.ID)
- t.AddValue("Name", activity.ActivityName)
- t.Output(os.Stdout)
-}
-
-func activitiesViewWeather(_ *cobra.Command, args []string) {
- activityID, err := strconv.Atoi(args[0])
- bail(err)
-
- weather, err := client.ActivityWeather(activityID)
- bail(err)
-
- t := NewTabular()
- t.AddValueUnit("Temperature", weather.Temperature, "°F")
- t.AddValueUnit("Apparent Temperature", weather.ApparentTemperature, "°F")
- t.AddValueUnit("Dew Point", weather.DewPoint, "°F")
- t.AddValueUnit("Relative Humidity", weather.RelativeHumidity, "%")
- t.AddValueUnit("Wind Direction", weather.WindDirection, weather.WindDirectionCompassPoint)
- t.AddValueUnit("Wind Speed", weather.WindSpeed, "mph")
- t.AddValue("Latitude", weather.Latitude)
- t.AddValue("Longitude", weather.Longitude)
- t.Output(os.Stdout)
-}
-
-func activitiesViewHRZones(_ *cobra.Command, args []string) {
- activityID, err := strconv.Atoi(args[0])
- bail(err)
-
- zones, err := client.ActivityHrZones(activityID)
- bail(err)
-
- t := NewTabular()
- //for (zone in zones)
- for i := 0; i < len(zones)-1; i++ {
- t.AddValue(fmt.Sprintf("Zone %d (%3d-%3dbpm)", zones[i].ZoneNumber, zones[i].ZoneLowBoundary, zones[i+1].ZoneLowBoundary),
- zones[i].TimeInZone)
- }
- t.AddValue(fmt.Sprintf("Zone %d ( > %dbpm )", zones[len(zones)-1].ZoneNumber, zones[len(zones)-1].ZoneLowBoundary),
- zones[len(zones)-1].TimeInZone)
-
- t.Output(os.Stdout)
-}
-
-func activitiesExport(_ *cobra.Command, args []string) {
- format, err := connect.FormatFromExtension(exportFormat)
- bail(err)
-
- activityID, err := strconv.Atoi(args[0])
- bail(err)
-
- name := fmt.Sprintf("%d.%s", activityID, format.Extension())
- f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
- bail(err)
-
- err = client.ExportActivity(activityID, f, format)
- bail(err)
-}
-
-func activitiesImport(_ *cobra.Command, args []string) {
- filename := args[0]
-
- f, err := os.Open(filename)
- bail(err)
-
- format, err := connect.FormatFromFilename(filename)
- bail(err)
-
- id, err := client.ImportActivity(f, format)
- bail(err)
-
- fmt.Printf("Activity ID %d imported\n", id)
-}
-
-func activitiesDelete(_ *cobra.Command, args []string) {
- activityID, err := strconv.Atoi(args[0])
- bail(err)
-
- err = client.DeleteActivity(activityID)
- bail(err)
-}
-
-func activitiesRename(_ *cobra.Command, args []string) {
- activityID, err := strconv.Atoi(args[0])
- bail(err)
-
- newName := args[1]
-
- err = client.RenameActivity(activityID, newName)
- bail(err)
-}
diff --git a/python-garmin-connect/connect/badges.go b/python-garmin-connect/connect/badges.go
deleted file mode 100644
index 2406879..0000000
--- a/python-garmin-connect/connect/badges.go
+++ /dev/null
@@ -1,222 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
- "strconv"
-
- "github.com/spf13/cobra"
-
- connect "github.com/abrander/garmin-connect"
-)
-
-const gotIt = "✓"
-
-func init() {
- badgesCmd := &cobra.Command{
- Use: "badges",
- }
- rootCmd.AddCommand(badgesCmd)
-
- badgesLeaderboardCmd := &cobra.Command{
- Use: "leaderboard",
- Short: "Show the current points leaderbaord among the authenticated users connections",
- Run: badgesLeaderboard,
- Args: cobra.NoArgs,
- }
- badgesCmd.AddCommand(badgesLeaderboardCmd)
-
- badgesEarnedCmd := &cobra.Command{
- Use: "earned [display name]",
- Short: "Show the earned badges",
- Run: badgesEarned,
- Args: cobra.RangeArgs(0, 1),
- }
- badgesCmd.AddCommand(badgesEarnedCmd)
-
- badgesAvailableCmd := &cobra.Command{
- Use: "available",
- Short: "Show badges not yet earned",
- Run: badgesAvailable,
- Args: cobra.NoArgs,
- }
- badgesCmd.AddCommand(badgesAvailableCmd)
-
- badgesViewCmd := &cobra.Command{
- Use: "view ",
- Short: "Show details about a badge",
- Run: badgesView,
- Args: cobra.ExactArgs(1),
- }
- badgesCmd.AddCommand(badgesViewCmd)
-
- badgesCompareCmd := &cobra.Command{
- Use: "compare ",
- Short: "Compare the authenticated users badges with the badges of another user",
- Run: badgesCompare,
- Args: cobra.ExactArgs(1),
- }
- badgesCmd.AddCommand(badgesCompareCmd)
-}
-
-func badgesLeaderboard(_ *cobra.Command, _ []string) {
- leaderboard, err := client.BadgeLeaderBoard()
- bail(err)
-
- t := NewTable()
- t.AddHeader("Display Name", "Name", "Level", "Points")
- for _, status := range leaderboard {
- t.AddRow(status.DisplayName, status.Fullname, status.Level, status.Point)
- }
- t.Output(os.Stdout)
-}
-
-func badgesEarned(_ *cobra.Command, args []string) {
- var badges []connect.Badge
-
- if len(args) == 1 {
- displayName := args[0]
- // If we have a displayid to show, we abuse the compare call to read
- // badges earned by a connection.
- _, status, err := client.BadgeCompare(displayName)
- bail(err)
-
- badges = status.Badges
- } else {
- var err error
- badges, err = client.BadgesEarned()
- bail(err)
- }
-
- t := NewTable()
- t.AddHeader("ID", "Badge", "Points", "Date")
- for _, badge := range badges {
- p := fmt.Sprintf("%d", badge.Points)
- if badge.EarnedNumber > 1 {
- p = fmt.Sprintf("%d x%d", badge.Points, badge.EarnedNumber)
- }
- t.AddRow(badge.ID, badge.Name, p, badge.EarnedDate.String())
- }
- t.Output(os.Stdout)
-}
-
-func badgesAvailable(_ *cobra.Command, _ []string) {
- badges, err := client.BadgesAvailable()
- bail(err)
-
- t := NewTable()
- t.AddHeader("ID", "Key", "Name", "Points")
- for _, badge := range badges {
- t.AddRow(badge.ID, badge.Key, badge.Name, badge.Points)
- }
- t.Output(os.Stdout)
-}
-
-func badgesView(_ *cobra.Command, args []string) {
- badgeID, err := strconv.Atoi(args[0])
- bail(err)
-
- badge, err := client.BadgeDetail(badgeID)
- bail(err)
-
- t := NewTabular()
- t.AddValue("ID", badge.ID)
- t.AddValue("Key", badge.Key)
- t.AddValue("Name", badge.Name)
- t.AddValue("Points", badge.Points)
- t.AddValue("Earned", formatDate(badge.EarnedDate.Time))
- t.AddValueUnit("Earned", badge.EarnedNumber, "time(s)")
- t.AddValue("Available from", formatDate(badge.Start.Time))
- t.AddValue("Available to", formatDate(badge.End.Time))
- t.Output(os.Stdout)
-
- if len(badge.Connections) > 0 {
- fmt.Printf("\n Connections with badge:\n")
- t := NewTable()
- t.AddHeader("Display Name", "Name", "Earned")
- for _, b := range badge.Connections {
- t.AddRow(b.DisplayName, b.FullName, b.EarnedDate.Time)
- }
- t.Output(os.Stdout)
- }
-
- if len(badge.RelatedBadges) > 0 {
- fmt.Printf("\n Relates badges:\n")
-
- t := NewTable()
- t.AddHeader("ID", "Key", "Name", "Points", "Earned")
- for _, b := range badge.RelatedBadges {
- earned := ""
- if b.EarnedByMe {
- earned = gotIt
- }
- t.AddRow(b.ID, b.Key, b.Name, b.Points, earned)
- }
- t.Output(os.Stdout)
- }
-}
-
-func badgesCompare(_ *cobra.Command, args []string) {
- displayName := args[0]
- a, b, err := client.BadgeCompare(displayName)
- bail(err)
-
- t := NewTable()
- t.AddHeader("Badge", a.Fullname, b.Fullname, "Points")
-
- type status struct {
- name string
- points int
- me bool
- meEarned int
- other bool
- otherEarned int
- }
-
- m := map[string]*status{}
-
- for _, badge := range a.Badges {
- s, found := m[badge.Key]
- if !found {
- s = &status{}
- m[badge.Key] = s
- }
- s.me = true
- s.meEarned = badge.EarnedNumber
- s.name = badge.Name
- s.points = badge.Points
- }
-
- for _, badge := range b.Badges {
- s, found := m[badge.Key]
- if !found {
- s = &status{}
- m[badge.Key] = s
- }
- s.other = true
- s.otherEarned = badge.EarnedNumber
- s.name = badge.Name
- s.points = badge.Points
- }
-
- for _, e := range m {
- var me string
- var other string
- if e.me {
- me = gotIt
- if e.meEarned > 1 {
- me += fmt.Sprintf(" %dx", e.meEarned)
- }
- }
-
- if e.other {
- other = gotIt
- if e.otherEarned > 1 {
- other += fmt.Sprintf(" %dx", e.otherEarned)
- }
- }
- t.AddRow(e.name, me, other, e.points)
- }
-
- t.Output(os.Stdout)
-}
diff --git a/python-garmin-connect/connect/calendar.go b/python-garmin-connect/connect/calendar.go
deleted file mode 100644
index 135992a..0000000
--- a/python-garmin-connect/connect/calendar.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package main
-
-import (
- "os"
- "strconv"
-
- "github.com/spf13/cobra"
-)
-
-func init() {
- calendarCmd := &cobra.Command{
- Use: "calendar",
- }
- rootCmd.AddCommand(calendarCmd)
-
- calendarYearCmd := &cobra.Command{
- Use: "year ",
- Short: "List active days in the year",
- Run: calendarYear,
- Args: cobra.RangeArgs(1, 1),
- }
- calendarCmd.AddCommand(calendarYearCmd)
-
- calendarMonthCmd := &cobra.Command{
- Use: "month ",
- Short: "List active days in the month",
- Run: calendarMonth,
- Args: cobra.RangeArgs(2, 2),
- }
- calendarCmd.AddCommand(calendarMonthCmd)
-
- calendarWeekCmd := &cobra.Command{
- Use: "week ",
- Short: "List active days in the week",
- Run: calendarWeek,
- Args: cobra.RangeArgs(3, 3),
- }
- calendarCmd.AddCommand(calendarWeekCmd)
-
-}
-
-func calendarYear(_ *cobra.Command, args []string) {
- year, err := strconv.ParseInt(args[0], 10, 32)
- bail(err)
-
- calendar, err := client.CalendarYear(int(year))
- bail(err)
-
- t := NewTable()
- t.AddHeader("ActivityType ID", "Number of Activities", "Total Distance", "Total Duration", "Total Calories")
- for _, summary := range calendar.YearSummaries {
- t.AddRow(
- summary.ActivityTypeID,
- summary.NumberOfActivities,
- summary.TotalDistance,
- summary.TotalDuration,
- summary.TotalCalories,
- )
- }
- t.Output(os.Stdout)
-}
-
-func calendarMonth(_ *cobra.Command, args []string) {
- year, err := strconv.ParseInt(args[0], 10, 32)
- bail(err)
-
- month, err := strconv.ParseInt(args[1], 10, 32)
- bail(err)
-
- calendar, err := client.CalendarMonth(int(year), int(month))
- bail(err)
-
- t := NewTable()
- t.AddHeader("ID", "Date", "Name", "Distance", "Time", "Calories")
- for _, item := range calendar.CalendarItems {
- t.AddRow(
- item.ID,
- item.Date,
- item.Title,
- item.Distance,
- item.ElapsedDuration,
- item.Calories,
- )
- }
- t.Output(os.Stdout)
-}
-
-func calendarWeek(_ *cobra.Command, args []string) {
- year, err := strconv.ParseInt(args[0], 10, 32)
- bail(err)
-
- month, err := strconv.ParseInt(args[1], 10, 32)
- bail(err)
-
- week, err := strconv.ParseInt(args[2], 10, 32)
- bail(err)
-
- calendar, err := client.CalendarWeek(int(year), int(month), int(week))
- bail(err)
-
- t := NewTable()
- t.AddHeader("ID", "Date", "Name", "Distance", "Time", "Calories")
- for _, item := range calendar.CalendarItems {
- t.AddRow(
- item.ID,
- item.Date,
- item.Title,
- item.Distance,
- item.ElapsedDuration,
- item.Calories,
- )
- }
- t.Output(os.Stdout)
-}
diff --git a/python-garmin-connect/connect/challenges.go b/python-garmin-connect/connect/challenges.go
deleted file mode 100644
index dfc696c..0000000
--- a/python-garmin-connect/connect/challenges.go
+++ /dev/null
@@ -1,169 +0,0 @@
-package main
-
-import (
- "os"
- "strconv"
- "strings"
-
- "github.com/spf13/cobra"
-)
-
-func init() {
- challengesCmd := &cobra.Command{
- Use: "challenges",
- }
- rootCmd.AddCommand(challengesCmd)
-
- challengesListCmd := &cobra.Command{
- Use: "list",
- Short: "List ad-hoc challenges",
- Run: challengesList,
- Args: cobra.NoArgs,
- }
- challengesCmd.AddCommand(challengesListCmd)
-
- challengesListInvitesCmd := &cobra.Command{
- Use: "invites",
- Short: "List ad-hoc challenge invites",
- Run: challengesListInvites,
- Args: cobra.NoArgs,
- }
- challengesListCmd.AddCommand(challengesListInvitesCmd)
-
- challengesAcceptCmd := &cobra.Command{
- Use: "accept ",
- Short: "Accept an ad-hoc challenge",
- Run: challengesAccept,
- Args: cobra.ExactArgs(1),
- }
- challengesCmd.AddCommand(challengesAcceptCmd)
-
- challengesDeclineCmd := &cobra.Command{
- Use: "decline ",
- Short: "Decline an ad-hoc challenge",
- Run: challengesDecline,
- Args: cobra.ExactArgs(1),
- }
- challengesCmd.AddCommand(challengesDeclineCmd)
-
- challengesListPreviousCmd := &cobra.Command{
- Use: "previous",
- Short: "Show completed ad-hoc challenges",
- Run: challengesListPrevious,
- Args: cobra.NoArgs,
- }
- challengesListCmd.AddCommand(challengesListPreviousCmd)
-
- challengesViewCmd := &cobra.Command{
- Use: "view ",
- Short: "View challenge details",
- Run: challengesView,
- Args: cobra.ExactArgs(1),
- }
- challengesCmd.AddCommand(challengesViewCmd)
-
- challengesLeaveCmd := &cobra.Command{
- Use: "leave ",
- Short: "Leave a challenge",
- Run: challengesLeave,
- Args: cobra.ExactArgs(1),
- }
- challengesCmd.AddCommand(challengesLeaveCmd)
-
- challengesRemoveCmd := &cobra.Command{
- Use: "remove ",
- Short: "Remove a user from a challenge",
- Run: challengesRemove,
- Args: cobra.ExactArgs(2),
- }
- challengesCmd.AddCommand(challengesRemoveCmd)
-}
-
-func challengesList(_ *cobra.Command, args []string) {
- challenges, err := client.AdhocChallenges()
- bail(err)
-
- t := NewTable()
- t.AddHeader("ID", "Start", "End", "Description", "Name", "Rank")
- for _, c := range challenges {
- t.AddRow(c.UUID, c.Start, c.End, c.Description, c.Name, c.UserRanking)
- }
- t.Output(os.Stdout)
-}
-
-func challengesListInvites(_ *cobra.Command, _ []string) {
- challenges, err := client.AdhocChallengeInvites()
- bail(err)
-
- t := NewTable()
- t.AddHeader("Invite ID", "Challenge ID", "Start", "End", "Description", "Name", "Rank")
- for _, c := range challenges {
- t.AddRow(c.InviteID, c.UUID, c.Start, c.End, c.Description, c.Name, c.UserRanking)
- }
- t.Output(os.Stdout)
-}
-
-func challengesAccept(_ *cobra.Command, args []string) {
- inviteID, err := strconv.Atoi(args[0])
- bail(err)
-
- err = client.AdhocChallengeInvitationRespond(inviteID, true)
- bail(err)
-}
-
-func challengesDecline(_ *cobra.Command, args []string) {
- inviteID, err := strconv.Atoi(args[0])
- bail(err)
-
- err = client.AdhocChallengeInvitationRespond(inviteID, false)
- bail(err)
-}
-
-func challengesListPrevious(_ *cobra.Command, args []string) {
- challenges, err := client.HistoricalAdhocChallenges()
- bail(err)
-
- t := NewTable()
- t.AddHeader("ID", "Start", "End", "Description", "Name", "Rank")
- for _, c := range challenges {
- t.AddRow(c.UUID, c.Start, c.End, c.Description, c.Name, c.UserRanking)
- }
- t.Output(os.Stdout)
-}
-
-func challengesLeave(_ *cobra.Command, args []string) {
- uuid := args[0]
- err := client.LeaveAdhocChallenge(uuid, 0)
- bail(err)
-}
-
-func challengesRemove(_ *cobra.Command, args []string) {
- uuid := args[0]
-
- profileID, err := strconv.ParseInt(args[1], 10, 64)
- bail(err)
-
- err = client.LeaveAdhocChallenge(uuid, profileID)
- bail(err)
-}
-
-func challengesView(_ *cobra.Command, args []string) {
- uuid := args[0]
- challenge, err := client.AdhocChallenge(uuid)
- bail(err)
-
- players := make([]string, len(challenge.Players))
- for i, player := range challenge.Players {
- players[i] = player.FullName + " [" + player.DisplayName + "]"
- }
-
- t := NewTabular()
- t.AddValue("ID", challenge.UUID)
- t.AddValue("Start", challenge.Start.String())
- t.AddValue("End", challenge.End.String())
- t.AddValue("Description", challenge.Description)
- t.AddValue("Name", challenge.Name)
- t.AddValue("Rank", challenge.UserRanking)
- t.AddValue("Players", strings.Join(players, ", "))
- t.Output(os.Stdout)
-}
diff --git a/python-garmin-connect/connect/completion.go b/python-garmin-connect/connect/completion.go
deleted file mode 100644
index f5b6129..0000000
--- a/python-garmin-connect/connect/completion.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package main
-
-import (
- "os"
-
- "github.com/spf13/cobra"
-)
-
-func init() {
- completionCmd := &cobra.Command{
- Use: "completion",
- }
- rootCmd.AddCommand(completionCmd)
-
- completionBashCmd := &cobra.Command{
- Use: "bash",
- Short: "Output command completion for Bourne Again Shell (bash)",
- RunE: completionBash,
- Args: cobra.NoArgs,
- }
- completionCmd.AddCommand(completionBashCmd)
-
- completionZshCmd := &cobra.Command{
- Use: "zsh",
- Short: "Output command completion for Z Shell (zsh)",
- RunE: completionZsh,
- Args: cobra.NoArgs,
- }
- completionCmd.AddCommand(completionZshCmd)
-}
-
-func completionBash(_ *cobra.Command, _ []string) error {
- return rootCmd.GenBashCompletion(os.Stdout)
-}
-
-func completionZsh(_ *cobra.Command, _ []string) error {
- return rootCmd.GenZshCompletion(os.Stdout)
-}
diff --git a/python-garmin-connect/connect/connections.go b/python-garmin-connect/connect/connections.go
deleted file mode 100644
index eb0523d..0000000
--- a/python-garmin-connect/connect/connections.go
+++ /dev/null
@@ -1,180 +0,0 @@
-package main
-
-import (
- "os"
- "strconv"
-
- "github.com/spf13/cobra"
-)
-
-func init() {
- connectionsCmd := &cobra.Command{
- Use: "connections",
- }
- rootCmd.AddCommand(connectionsCmd)
-
- connectionsListCmd := &cobra.Command{
- Use: "list [display name]",
- Short: "List all connections",
- Run: connectionsList,
- Args: cobra.RangeArgs(0, 1),
- }
- connectionsCmd.AddCommand(connectionsListCmd)
-
- connectionsPendingCmd := &cobra.Command{
- Use: "pending",
- Short: "List pending connections",
- Run: connectionsPending,
- Args: cobra.NoArgs,
- }
- connectionsCmd.AddCommand(connectionsPendingCmd)
-
- connectionsRemoveCmd := &cobra.Command{
- Use: "remove ",
- Short: "Remove a connection",
- Run: connectionsRemove,
- Args: cobra.ExactArgs(1),
- }
- connectionsCmd.AddCommand(connectionsRemoveCmd)
-
- connectionsSearchCmd := &cobra.Command{
- Use: "search ",
- Short: "Search Garmin wide for a person",
- Run: connectionsSearch,
- Args: cobra.ExactArgs(1),
- }
- connectionsCmd.AddCommand(connectionsSearchCmd)
-
- connectionsAcceptCmd := &cobra.Command{
- Use: "accept ",
- Short: "Accept a connection request",
- Run: connectionsAccept,
- Args: cobra.ExactArgs(1),
- }
- connectionsCmd.AddCommand(connectionsAcceptCmd)
-
- connectionsRequestCmd := &cobra.Command{
- Use: "request ",
- Short: "Request connectio from another user",
- Run: connectionsRequest,
- Args: cobra.ExactArgs(1),
- }
- connectionsCmd.AddCommand(connectionsRequestCmd)
-
- blockedCmd := &cobra.Command{
- Use: "blocked",
- }
- connectionsCmd.AddCommand(blockedCmd)
-
- blockedListCmd := &cobra.Command{
- Use: "list",
- Short: "List currently blocked users",
- Run: connectionsBlockedList,
- Args: cobra.NoArgs,
- }
- blockedCmd.AddCommand(blockedListCmd)
-
- blockedAddCmd := &cobra.Command{
- Use: "add ",
- Short: "Add a user to the blocked list",
- Run: connectionsBlockedAdd,
- Args: cobra.ExactArgs(1),
- }
- blockedCmd.AddCommand(blockedAddCmd)
-
- blockedRemoveCmd := &cobra.Command{
- Use: "remove ",
- Short: "Remove a user from the blocked list",
- Run: connectionsBlockedRemove,
- Args: cobra.ExactArgs(1),
- }
- blockedCmd.AddCommand(blockedRemoveCmd)
-}
-
-func connectionsList(_ *cobra.Command, args []string) {
- displayName := ""
- if len(args) == 1 {
- displayName = args[0]
- }
-
- connections, err := client.Connections(displayName)
- bail(err)
-
- t := NewTable()
- t.AddHeader("Connection ID", "Display Name", "Name", "Location", "Profile Image")
- for _, c := range connections {
- t.AddRow(c.ConnectionRequestID, c.DisplayName, c.Fullname, c.Location, c.ProfileImageURLMedium)
- }
- t.Output(os.Stdout)
-}
-
-func connectionsPending(_ *cobra.Command, _ []string) {
- connections, err := client.PendingConnections()
- bail(err)
-
- t := NewTable()
- t.AddHeader("RequestID", "Display Name", "Name", "Location", "Profile Image")
- for _, c := range connections {
- t.AddRow(c.ConnectionRequestID, c.DisplayName, c.Fullname, c.Location, c.ProfileImageURLMedium)
- }
- t.Output(os.Stdout)
-}
-
-func connectionsRemove(_ *cobra.Command, args []string) {
- connectionRequestID, err := strconv.Atoi(args[0])
- bail(err)
-
- err = client.RemoveConnection(connectionRequestID)
- bail(err)
-}
-
-func connectionsSearch(_ *cobra.Command, args []string) {
- keyword := args[0]
- connections, err := client.SearchConnections(keyword)
- bail(err)
-
- t := NewTabular()
- for _, c := range connections {
- t.AddValue(c.DisplayName, c.Fullname)
- }
- t.Output(os.Stdout)
-}
-
-func connectionsAccept(_ *cobra.Command, args []string) {
- connectionRequestID, err := strconv.Atoi(args[0])
- bail(err)
-
- err = client.AcceptConnection(connectionRequestID)
- bail(err)
-}
-
-func connectionsRequest(_ *cobra.Command, args []string) {
- displayName := args[0]
-
- err := client.RequestConnection(displayName)
- bail(err)
-}
-
-func connectionsBlockedList(_ *cobra.Command, _ []string) {
- blockedUsers, err := client.BlockedUsers()
- bail(err)
-
- t := NewTable()
- t.AddHeader("Display Name", "Name", "Location", "Profile Image")
- for _, c := range blockedUsers {
- t.AddRow(c.DisplayName, c.Fullname, c.Location, c.ProfileImageURLMedium)
- }
- t.Output(os.Stdout)
-}
-
-func connectionsBlockedAdd(_ *cobra.Command, args []string) {
- displayName := args[0]
- err := client.BlockUser(displayName)
- bail(err)
-}
-
-func connectionsBlockedRemove(_ *cobra.Command, args []string) {
- displayName := args[0]
- err := client.UnblockUser(displayName)
- bail(err)
-}
diff --git a/python-garmin-connect/connect/gear.go b/python-garmin-connect/connect/gear.go
deleted file mode 100644
index 985b949..0000000
--- a/python-garmin-connect/connect/gear.go
+++ /dev/null
@@ -1,151 +0,0 @@
-package main
-
-import (
- "os"
- "sort"
- "strconv"
-
- "github.com/spf13/cobra"
-)
-
-func init() {
- gearCmd := &cobra.Command{
- Use: "gear",
- }
- rootCmd.AddCommand(gearCmd)
-
- gearListCmd := &cobra.Command{
- Use: "list [profile ID]",
- Short: "List Gear",
- Run: gearList,
- Args: cobra.RangeArgs(0, 1),
- }
- gearCmd.AddCommand(gearListCmd)
-
- gearTypeListCmd := &cobra.Command{
- Use: "types",
- Short: "List Gear Types",
- Run: gearTypeList,
- }
- gearCmd.AddCommand(gearTypeListCmd)
-
- gearLinkCommand := &cobra.Command{
- Use: "link ",
- Short: "Link Gear to Activity",
- Run: gearLink,
- Args: cobra.ExactArgs(2),
- }
- gearCmd.AddCommand(gearLinkCommand)
-
- gearUnlinkCommand := &cobra.Command{
- Use: "unlink ",
- Short: "Unlink Gear to Activity",
- Run: gearUnlink,
- Args: cobra.ExactArgs(2),
- }
- gearCmd.AddCommand(gearUnlinkCommand)
-
- gearForActivityCommand := &cobra.Command{
- Use: "activity ",
- Short: "Get Gear for Activity",
- Run: gearForActivity,
- Args: cobra.ExactArgs(1),
- }
- gearCmd.AddCommand(gearForActivityCommand)
-}
-
-func gearList(_ *cobra.Command, args []string) {
- var profileID int64 = 0
- var err error
- if len(args) == 1 {
- profileID, err = strconv.ParseInt(args[0], 10, 64)
- bail(err)
- }
- gear, err := client.Gear(profileID)
- bail(err)
-
- t := NewTable()
- t.AddHeader("UUID", "Type", "Brand & Model", "Nickname", "Created Date", "Total Distance", "Activities")
- for _, g := range gear {
-
- gearStats, err := client.GearStats(g.Uuid)
- bail(err)
-
- t.AddRow(
- g.Uuid,
- g.GearTypeName,
- g.CustomMakeModel,
- g.DisplayName,
- g.CreateDate.Time,
- strconv.FormatFloat(gearStats.TotalDistance, 'f', 2, 64),
- gearStats.TotalActivities,
- )
- }
- t.Output(os.Stdout)
-}
-
-func gearTypeList(_ *cobra.Command, _ []string) {
- gearTypes, err := client.GearType()
- bail(err)
-
- t := NewTable()
- t.AddHeader("ID", "Name", "Created Date", "Update Date")
- sort.Slice(gearTypes, func(i, j int) bool {
- return gearTypes[i].TypeID < gearTypes[j].TypeID
- })
-
- for _, g := range gearTypes {
- t.AddRow(
- g.TypeID,
- g.TypeName,
- g.CreateDate,
- g.UpdateDate,
- )
- }
- t.Output(os.Stdout)
-}
-
-func gearLink(_ *cobra.Command, args []string) {
- uuid := args[0]
- activityID, err := strconv.Atoi(args[1])
- bail(err)
-
- err = client.GearLink(uuid, activityID)
- bail(err)
-}
-
-func gearUnlink(_ *cobra.Command, args []string) {
- uuid := args[0]
- activityID, err := strconv.Atoi(args[1])
- bail(err)
-
- err = client.GearUnlink(uuid, activityID)
- bail(err)
-}
-
-func gearForActivity(_ *cobra.Command, args []string) {
- activityID, err := strconv.Atoi(args[0])
- bail(err)
-
- gear, err := client.GearForActivity(0, activityID)
- bail(err)
-
- t := NewTable()
- t.AddHeader("UUID", "Type", "Brand & Model", "Nickname", "Created Date", "Total Distance", "Activities")
- for _, g := range gear {
-
- gearStats, err := client.GearStats(g.Uuid)
- bail(err)
-
- t.AddRow(
- g.Uuid,
- g.GearTypeName,
- g.CustomMakeModel,
- g.DisplayName,
- g.CreateDate.Time,
- strconv.FormatFloat(gearStats.TotalDistance, 'f', 2, 64),
- gearStats.TotalActivities,
- )
- }
- t.Output(os.Stdout)
-}
diff --git a/python-garmin-connect/connect/get.go b/python-garmin-connect/connect/get.go
deleted file mode 100644
index b9fbb2c..0000000
--- a/python-garmin-connect/connect/get.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package main
-
-import (
- "bytes"
- "encoding/json"
- "io"
- "os"
-
- "github.com/spf13/cobra"
-)
-
-var (
- formatJSON bool
-)
-
-func init() {
- getCmd := &cobra.Command{
- Use: "get ",
- Short: "Get data from Garmin Connect, print to stdout",
- Run: get,
- Args: cobra.ExactArgs(1),
- }
- getCmd.Flags().BoolVarP(&formatJSON, "json", "j", false, "Format output as indented JSON")
- rootCmd.AddCommand(getCmd)
-}
-
-func get(_ *cobra.Command, args []string) {
- url := args[0]
-
- if formatJSON {
- raw := bytes.NewBuffer(nil)
- buffer := bytes.NewBuffer(nil)
-
- err := client.Download(url, raw)
- bail(err)
-
- err = json.Indent(buffer, raw.Bytes(), "", " ")
- bail(err)
-
- _, err = io.Copy(os.Stdout, buffer)
- bail(err)
- } else {
- err := client.Download(url, os.Stdout)
- bail(err)
- }
-}
diff --git a/python-garmin-connect/connect/goals.go b/python-garmin-connect/connect/goals.go
deleted file mode 100644
index 931d71c..0000000
--- a/python-garmin-connect/connect/goals.go
+++ /dev/null
@@ -1,67 +0,0 @@
-package main
-
-import (
- "os"
- "strconv"
-
- "github.com/spf13/cobra"
-)
-
-func init() {
- goalsCmd := &cobra.Command{
- Use: "goals",
- }
- rootCmd.AddCommand(goalsCmd)
-
- goalsListCmd := &cobra.Command{
- Use: "list [display name]",
- Short: "List all goals",
- Run: goalsList,
- Args: cobra.RangeArgs(0, 1),
- }
- goalsCmd.AddCommand(goalsListCmd)
-
- goalsDeleteCmd := &cobra.Command{
- Use: "delete ",
- Short: "Delete a goal",
- Run: goalsDelete,
- Args: cobra.ExactArgs(1),
- }
- goalsCmd.AddCommand(goalsDeleteCmd)
-}
-
-func goalsList(_ *cobra.Command, args []string) {
- displayName := ""
- if len(args) == 1 {
- displayName = args[0]
- }
-
- t := NewTable()
- t.AddHeader("ID", "Profile", "Category", "Type", "Start", "End", "Created", "Value")
- for typ := 0; typ <= 9; typ++ {
- goals, err := client.Goals(displayName, typ)
- bail(err)
-
- for _, g := range goals {
- t.AddRow(
- g.ID,
- g.ProfileID,
- g.GoalCategory,
- g.GoalType,
- g.Start,
- g.End,
- g.Created,
- g.Value,
- )
- }
- }
- t.Output(os.Stdout)
-}
-
-func goalsDelete(_ *cobra.Command, args []string) {
- goalID, err := strconv.Atoi(args[0])
- bail(err)
-
- err = client.DeleteGoal("", goalID)
- bail(err)
-}
diff --git a/python-garmin-connect/connect/groups.go b/python-garmin-connect/connect/groups.go
deleted file mode 100644
index 4383ab4..0000000
--- a/python-garmin-connect/connect/groups.go
+++ /dev/null
@@ -1,189 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
- "strconv"
- "strings"
-
- "github.com/spf13/cobra"
-)
-
-func init() {
- groupsCmd := &cobra.Command{
- Use: "groups",
- }
- rootCmd.AddCommand(groupsCmd)
-
- groupsListCmd := &cobra.Command{
- Use: "list [display name]",
- Short: "List all groups",
- Run: groupsList,
- Args: cobra.RangeArgs(0, 1),
- }
- groupsCmd.AddCommand(groupsListCmd)
-
- groupsViewCmd := &cobra.Command{
- Use: "view ",
- Short: "View group details",
- Run: groupsView,
- Args: cobra.ExactArgs(1),
- }
- groupsCmd.AddCommand(groupsViewCmd)
-
- groupsViewAnnouncementCmd := &cobra.Command{
- Use: "announcement ",
- Short: "View group abbouncement",
- Run: groupsViewAnnouncement,
- Args: cobra.ExactArgs(1),
- }
- groupsViewCmd.AddCommand(groupsViewAnnouncementCmd)
-
- groupsViewMembersCmd := &cobra.Command{
- Use: "members ",
- Short: "View group members",
- Run: groupsViewMembers,
- Args: cobra.ExactArgs(1),
- }
- groupsViewCmd.AddCommand(groupsViewMembersCmd)
-
- groupsSearchCmd := &cobra.Command{
- Use: "search ",
- Short: "Search for a group",
- Run: groupsSearch,
- Args: cobra.ExactArgs(1),
- }
- groupsCmd.AddCommand(groupsSearchCmd)
-
- groupsJoinCmd := &cobra.Command{
- Use: "join ",
- Short: "Join a group",
- Run: groupsJoin,
- Args: cobra.ExactArgs(1),
- }
- groupsCmd.AddCommand(groupsJoinCmd)
-
- groupsLeaveCmd := &cobra.Command{
- Use: "leave ",
- Short: "Leave a group",
- Run: groupsLeave,
- Args: cobra.ExactArgs(1),
- }
- groupsCmd.AddCommand(groupsLeaveCmd)
-}
-
-func groupsList(_ *cobra.Command, args []string) {
- displayName := ""
- if len(args) == 1 {
- displayName = args[0]
- }
-
- groups, err := client.Groups(displayName)
- bail(err)
-
- t := NewTable()
- t.AddHeader("ID", "Name", "Description", "Profile Image")
- for _, g := range groups {
- t.AddRow(g.ID, g.Name, g.Description, g.ProfileImageURLLarge)
- }
- t.Output(os.Stdout)
-}
-
-func groupsSearch(_ *cobra.Command, args []string) {
- keyword := args[0]
- groups, err := client.SearchGroups(keyword)
- bail(err)
-
- lastID := 0
-
- t := NewTable()
- t.AddHeader("ID", "Name", "Description", "Profile Image")
- for _, g := range groups {
- if g.ID == lastID {
- continue
- }
-
- t.AddRow(g.ID, g.Name, g.Description, g.ProfileImageURLLarge)
-
- lastID = g.ID
- }
- t.Output(os.Stdout)
-}
-
-func groupsView(_ *cobra.Command, args []string) {
- id, err := strconv.Atoi(args[0])
- bail(err)
-
- group, err := client.Group(id)
- bail(err)
-
- t := NewTabular()
- t.AddValue("ID", group.ID)
- t.AddValue("Name", group.Name)
- t.AddValue("Description", group.Description)
- t.AddValue("OwnerID", group.OwnerID)
- t.AddValue("ProfileImageURLLarge", group.ProfileImageURLLarge)
- t.AddValue("ProfileImageURLMedium", group.ProfileImageURLMedium)
- t.AddValue("ProfileImageURLSmall", group.ProfileImageURLSmall)
- t.AddValue("Visibility", group.Visibility)
- t.AddValue("Privacy", group.Privacy)
- t.AddValue("Location", group.Location)
- t.AddValue("WebsiteURL", group.WebsiteURL)
- t.AddValue("FacebookURL", group.FacebookURL)
- t.AddValue("TwitterURL", group.TwitterURL)
- // t.AddValue("PrimaryActivities", group.PrimaryActivities)
- t.AddValue("OtherPrimaryActivity", group.OtherPrimaryActivity)
- // t.AddValue("LeaderboardTypes", group.LeaderboardTypes)
- // t.AddValue("FeatureTypes", group.FeatureTypes)
- t.AddValue("CorporateWellness", group.CorporateWellness)
- // t.AddValue("ActivityFeedTypes", group.ActivityFeedTypes)
- t.Output(os.Stdout)
-}
-
-func groupsViewAnnouncement(_ *cobra.Command, args []string) {
- id, err := strconv.Atoi(args[0])
- bail(err)
-
- announcement, err := client.GroupAnnouncement(id)
- bail(err)
-
- t := NewTabular()
- t.AddValue("ID", announcement.ID)
- t.AddValue("GroupID", announcement.GroupID)
- t.AddValue("Title", announcement.Title)
- t.AddValue("ExpireDate", announcement.ExpireDate.String())
- t.AddValue("AnnouncementDate", announcement.AnnouncementDate.String())
- t.Output(os.Stdout)
- fmt.Fprintf(os.Stdout, "\n%s\n", strings.TrimSpace(announcement.Message))
-}
-
-func groupsViewMembers(_ *cobra.Command, args []string) {
- id, err := strconv.Atoi(args[0])
- bail(err)
-
- members, err := client.GroupMembers(id)
- bail(err)
-
- t := NewTable()
- t.AddHeader("Display Name", "Joined", "Name", "Location", "Role", "Profile Image")
- for _, m := range members {
- t.AddRow(m.DisplayName, m.Joined, m.Fullname, m.Location, m.Role, m.ProfileImageURLMedium)
- }
- t.Output(os.Stdout)
-}
-
-func groupsJoin(_ *cobra.Command, args []string) {
- groupID, err := strconv.Atoi(args[0])
- bail(err)
-
- err = client.JoinGroup(groupID)
- bail(err)
-}
-
-func groupsLeave(_ *cobra.Command, args []string) {
- groupID, err := strconv.Atoi(args[0])
- bail(err)
-
- err = client.LeaveGroup(groupID)
- bail(err)
-}
diff --git a/python-garmin-connect/connect/info.go b/python-garmin-connect/connect/info.go
deleted file mode 100644
index b0269e8..0000000
--- a/python-garmin-connect/connect/info.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package main
-
-import (
- "os"
- "time"
-
- connect "github.com/abrander/garmin-connect"
- "github.com/spf13/cobra"
-)
-
-func init() {
- infoCmd := &cobra.Command{
- Use: "info [display name]",
- Short: "Show various information and statistics about a Connect User",
- Run: info,
- Args: cobra.RangeArgs(0, 1),
- }
- rootCmd.AddCommand(infoCmd)
-}
-
-func info(_ *cobra.Command, args []string) {
- displayName := ""
- if len(args) == 1 {
- displayName = args[0]
- }
-
- t := NewTabular()
-
- socialProfile, err := client.SocialProfile(displayName)
- if err == connect.ErrNotFound {
- bail(err)
- }
-
- if err == nil {
- displayName = socialProfile.DisplayName
- } else {
- socialProfile, err = client.PublicSocialProfile(displayName)
- bail(err)
-
- displayName = socialProfile.DisplayName
- }
-
- t.AddValue("ID", socialProfile.ID)
- t.AddValue("Profile ID", socialProfile.ProfileID)
- t.AddValue("Display Name", socialProfile.DisplayName)
- t.AddValue("Name", socialProfile.Fullname)
- t.AddValue("Level", socialProfile.UserLevel)
- t.AddValue("Points", socialProfile.UserPoint)
- t.AddValue("Profile Image", socialProfile.ProfileImageURLLarge)
-
- info, err := client.PersonalInformation(displayName)
- if err == nil {
- t.AddValue("", "")
- t.AddValue("Gender", info.UserInfo.Gender)
- t.AddValueUnit("Age", info.UserInfo.Age, "years")
- t.AddValueUnit("Height", nzf(info.BiometricProfile.Height), "cm")
- t.AddValueUnit("Weight", nzf(info.BiometricProfile.Weight/1000.0), "kg")
- t.AddValueUnit("Vo² Max", nzf(info.BiometricProfile.VO2Max), "mL/kg/min")
- t.AddValueUnit("Vo² Max (cycling)", nzf(info.BiometricProfile.VO2MaxCycling), "mL/kg/min")
- }
-
- life, err := client.LifetimeActivities(displayName)
- if err == nil {
- t.AddValue("", "")
- t.AddValue("Activities", life.Activities)
- t.AddValueUnit("Distance", life.Distance/1000.0, "km")
- t.AddValueUnit("Time", (time.Duration(life.Duration) * time.Second).Round(time.Second).String(), "hms")
- t.AddValueUnit("Calories", life.Calories/4.184, "Kcal")
- t.AddValueUnit("Elev Gain", life.ElevationGain, "m")
- }
-
- totals, err := client.LifetimeTotals(displayName)
- if err == nil {
- t.AddValue("", "")
- t.AddValueUnit("Steps", totals.Steps, "steps")
- t.AddValueUnit("Distance", totals.Distance/1000.0, "km")
- t.AddValueUnit("Daily Goal Met", totals.GoalsMetInDays, "days")
- t.AddValueUnit("Active Days", totals.ActiveDays, "days")
- if totals.ActiveDays > 0 {
- t.AddValueUnit("Average Steps", totals.Steps/totals.ActiveDays, "steps")
- }
- t.AddValueUnit("Calories", totals.Calories, "kCal")
- }
-
- lastUsed, err := client.LastUsed(displayName)
- if err == nil {
- t.AddValue("", "")
- t.AddValue("Device ID", lastUsed.DeviceID)
- t.AddValue("Device", lastUsed.DeviceName)
- t.AddValue("Time", lastUsed.DeviceUploadTime.String())
- t.AddValue("Ago", time.Since(lastUsed.DeviceUploadTime.Time).Round(time.Second).String())
- t.AddValue("Image", lastUsed.ImageURL)
- }
-
- t.Output(os.Stdout)
-}
diff --git a/python-garmin-connect/connect/main.go b/python-garmin-connect/connect/main.go
deleted file mode 100644
index 7f96c9e..0000000
--- a/python-garmin-connect/connect/main.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package main
-
-import (
- "fmt"
- "log"
- "os"
- "syscall"
-
- "github.com/spf13/cobra"
- "golang.org/x/crypto/ssh/terminal"
-
- connect "github.com/abrander/garmin-connect"
-)
-
-var (
- rootCmd = &cobra.Command{
- Use: os.Args[0] + " [command]",
- Short: "CLI Client for Garmin Connect",
- PersistentPreRun: func(cmd *cobra.Command, args []string) {
- loadState()
- if verbose {
- logger := log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile)
- client.SetOptions(connect.DebugLogger(logger))
- }
-
- if dumpFile != "" {
- w, err := os.OpenFile(dumpFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
- bail(err)
- client.SetOptions(connect.DumpWriter(w))
- }
- },
- PersistentPostRun: func(_ *cobra.Command, _ []string) {
- storeState()
- },
- }
-
- verbose bool
- dumpFile string
-)
-
-func init() {
- rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose debug output")
- rootCmd.PersistentFlags().StringVarP(&dumpFile, "dump", "d", "", "File to dump requests and responses to")
-
- authenticateCmd := &cobra.Command{
- Use: "authenticate [email]",
- Short: "Authenticate against the Garmin API",
- Run: authenticate,
- Args: cobra.RangeArgs(0, 1),
- }
- rootCmd.AddCommand(authenticateCmd)
-
- signoutCmd := &cobra.Command{
- Use: "signout",
- Short: "Log out of the Garmin API and forget session and password",
- Run: signout,
- Args: cobra.NoArgs,
- }
- rootCmd.AddCommand(signoutCmd)
-}
-
-func bail(err error) {
- if err != nil {
- log.Fatalf("%s", err.Error())
- }
-}
-
-func main() {
- bail(rootCmd.Execute())
-}
-
-func authenticate(_ *cobra.Command, args []string) {
- var email string
- if len(args) == 1 {
- email = args[0]
- } else {
- fmt.Print("Email: ")
- fmt.Scanln(&email)
- }
-
- fmt.Print("Password: ")
-
- password, err := terminal.ReadPassword(syscall.Stdin)
- bail(err)
-
- client.SetOptions(connect.Credentials(email, string(password)))
- err = client.Authenticate()
- bail(err)
-
- fmt.Printf("\nSuccess\n")
-}
-
-func signout(_ *cobra.Command, _ []string) {
- _ = client.Signout()
- client.Password = ""
-}
diff --git a/python-garmin-connect/connect/nzf.go b/python-garmin-connect/connect/nzf.go
deleted file mode 100644
index 0871944..0000000
--- a/python-garmin-connect/connect/nzf.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package main
-
-import (
- "fmt"
-)
-
-// nzf is a type that will print "-" instead of 0.0 when used as a stringer.
-type nzf float64
-
-func (nzf nzf) String() string {
- if nzf != 0.0 {
- return fmt.Sprintf("%.01f", nzf)
- }
-
- return "-"
-}
diff --git a/python-garmin-connect/connect/sleep.go b/python-garmin-connect/connect/sleep.go
deleted file mode 100644
index 6e8b48f..0000000
--- a/python-garmin-connect/connect/sleep.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
-
- connect "github.com/abrander/garmin-connect"
- "github.com/spf13/cobra"
-)
-
-func init() {
- sleepCmd := &cobra.Command{
- Use: "sleep",
- }
- rootCmd.AddCommand(sleepCmd)
-
- sleepSummaryCmd := &cobra.Command{
- Use: "summary [displayName]",
- Short: "Show sleep summary for date",
- Run: sleepSummary,
- Args: cobra.RangeArgs(1, 2),
- }
- sleepCmd.AddCommand(sleepSummaryCmd)
-}
-
-func sleepSummary(_ *cobra.Command, args []string) {
- date, err := connect.ParseDate(args[0])
- bail(err)
-
- displayName := ""
-
- if len(args) > 1 {
- displayName = args[1]
- }
-
- summary, _, levels, err := client.SleepData(displayName, date.Time())
- bail(err)
-
- t := NewTabular()
- t.AddValue("Start", summary.StartGMT)
- t.AddValue("End", summary.EndGMT)
- t.AddValue("Sleep", hoursAndMinutes(summary.Sleep))
- t.AddValue("Nap", hoursAndMinutes(summary.Nap))
- t.AddValue("Unmeasurable", hoursAndMinutes(summary.Unmeasurable))
- t.AddValue("Deep", hoursAndMinutes(summary.Deep))
- t.AddValue("Light", hoursAndMinutes(summary.Light))
- t.AddValue("REM", hoursAndMinutes(summary.REM))
- t.AddValue("Awake", hoursAndMinutes(summary.Awake))
- t.AddValue("Confirmed", summary.Confirmed)
- t.AddValue("Confirmation Type", summary.Confirmation)
- t.AddValue("REM Data", summary.REMData)
- t.Output(os.Stdout)
-
- fmt.Fprintf(os.Stdout, "\n")
-
- t2 := NewTable()
- t2.AddHeader("Start", "End", "State", "Duration")
- for _, l := range levels {
- t2.AddRow(l.Start, l.End, l.State, hoursAndMinutes(l.End.Sub(l.Start.Time)))
- }
- t2.Output(os.Stdout)
-}
diff --git a/python-garmin-connect/connect/state.go b/python-garmin-connect/connect/state.go
deleted file mode 100644
index a3b60f1..0000000
--- a/python-garmin-connect/connect/state.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "io/ioutil"
- "log"
- "os"
- "path"
-
- connect "github.com/abrander/garmin-connect"
-)
-
-var (
- client = connect.NewClient(
- connect.AutoRenewSession(true),
- )
-
- stateFile string
-)
-
-func init() {
- rootCmd.PersistentFlags().StringVarP(&stateFile, "state", "s", stateFilename(), "State file to use")
-}
-
-func stateFilename() string {
- home, err := os.UserHomeDir()
- if err != nil {
- log.Fatalf("Could not detect home directory: %s", err.Error())
- }
-
- return path.Join(home, ".garmin-connect.json")
-}
-
-func loadState() {
- data, err := ioutil.ReadFile(stateFile)
- if err != nil {
- log.Printf("Could not open state file: %s", err.Error())
- return
- }
-
- err = json.Unmarshal(data, client)
- if err != nil {
- log.Fatalf("Could not unmarshal state: %s", err.Error())
- }
-}
-
-func storeState() {
- b, err := json.MarshalIndent(client, "", " ")
- if err != nil {
- log.Fatalf("Could not marshal state: %s", err.Error())
- }
-
- err = ioutil.WriteFile(stateFile, b, 0600)
- if err != nil {
- log.Fatalf("Could not write state file: %s", err.Error())
- }
-}
diff --git a/python-garmin-connect/connect/tools.go b/python-garmin-connect/connect/tools.go
deleted file mode 100644
index b318b66..0000000
--- a/python-garmin-connect/connect/tools.go
+++ /dev/null
@@ -1,70 +0,0 @@
-package main
-
-import (
- "fmt"
- "strconv"
- "time"
-)
-
-func formatDate(t time.Time) string {
- if t == (time.Time{}) {
- return "-"
- }
-
- return fmt.Sprintf("%04d-%02d-%02d", t.Year(), t.Month(), t.Day())
-}
-
-func stringer(value interface{}) string {
- stringer, ok := value.(fmt.Stringer)
- if ok {
- return stringer.String()
- }
-
- str := ""
- switch v := value.(type) {
- case string:
- str = v
- case int, int64:
- str = fmt.Sprintf("%d", v)
- case float64:
- str = strconv.FormatFloat(v, 'f', 1, 64)
- case bool:
- if v {
- str = gotIt
- }
- default:
- panic(fmt.Sprintf("no idea what to do about %T:%v", value, value))
- }
-
- return str
-}
-
-func sliceStringer(values []interface{}) []string {
- ret := make([]string, len(values))
-
- for i, value := range values {
- ret[i] = stringer(value)
- }
-
- return ret
-}
-
-func hoursAndMinutes(dur time.Duration) string {
- if dur == 0 {
- return "-"
- }
-
- if dur < 60*time.Minute {
- m := dur.Truncate(time.Minute)
-
- return fmt.Sprintf("%dm", m/time.Minute)
- }
-
- h := dur.Truncate(time.Hour)
- m := (dur - h).Truncate(time.Minute)
-
- h /= time.Hour
- m /= time.Minute
-
- return fmt.Sprintf("%dh%dm", h, m)
-}
diff --git a/python-garmin-connect/connect/weight.go b/python-garmin-connect/connect/weight.go
deleted file mode 100644
index 3245849..0000000
--- a/python-garmin-connect/connect/weight.go
+++ /dev/null
@@ -1,224 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
- "strconv"
- "time"
-
- connect "github.com/abrander/garmin-connect"
- "github.com/spf13/cobra"
-)
-
-func init() {
- weightCmd := &cobra.Command{
- Use: "weight",
- }
- rootCmd.AddCommand(weightCmd)
-
- weightLatestCmd := &cobra.Command{
- Use: "latest",
- Short: "Show the latest weight-in",
- Run: weightLatest,
- Args: cobra.NoArgs,
- }
- weightCmd.AddCommand(weightLatestCmd)
-
- weightLatestWeekCmd := &cobra.Command{
- Use: "week",
- Short: "Show average weight for the latest week",
- Run: weightLatestWeek,
- Args: cobra.NoArgs,
- }
- weightLatestCmd.AddCommand(weightLatestWeekCmd)
-
- weightAddCmd := &cobra.Command{
- Use: "add ",
- Short: "Add a simple weight for a specific date",
- Run: weightAdd,
- Args: cobra.ExactArgs(2),
- }
- weightCmd.AddCommand(weightAddCmd)
-
- weightDeleteCmd := &cobra.Command{
- Use: "delete ",
- Short: "Delete a weight-in",
- Run: weightDelete,
- Args: cobra.ExactArgs(1),
- }
- weightCmd.AddCommand(weightDeleteCmd)
-
- weightDateCmd := &cobra.Command{
- Use: "date [yyyy-mm-dd]",
- Short: "Show weight for a specific date",
- Run: weightDate,
- Args: cobra.ExactArgs(1),
- }
- weightCmd.AddCommand(weightDateCmd)
-
- weightRangeCmd := &cobra.Command{
- Use: "range [yyyy-mm-dd] [yyyy-mm-dd]",
- Short: "Show weight for a date range",
- Run: weightRange,
- Args: cobra.ExactArgs(2),
- }
- weightCmd.AddCommand(weightRangeCmd)
-
- weightGoalCmd := &cobra.Command{
- Use: "goal [displayName]",
- Short: "Show weight goal",
- Run: weightGoal,
- Args: cobra.RangeArgs(0, 1),
- }
- weightCmd.AddCommand(weightGoalCmd)
-
- weightGoalSetCmd := &cobra.Command{
- Use: "set [goal in gram]",
- Short: "Set weight goal",
- Run: weightGoalSet,
- Args: cobra.ExactArgs(1),
- }
- weightGoalCmd.AddCommand(weightGoalSetCmd)
-}
-
-func weightLatest(_ *cobra.Command, _ []string) {
- weightin, err := client.LatestWeight(time.Now())
- bail(err)
-
- t := NewTabular()
- t.AddValue("Date", weightin.Date.String())
- t.AddValueUnit("Weight", weightin.Weight/1000.0, "kg")
- t.AddValueUnit("BMI", weightin.BMI, "kg/m2")
- t.AddValueUnit("Fat", weightin.BodyFatPercentage, "%")
- t.AddValueUnit("Fat Mass", (weightin.Weight*weightin.BodyFatPercentage)/100000.0, "kg")
- t.AddValueUnit("Water", weightin.BodyWater, "%")
- t.AddValueUnit("Bone Mass", float64(weightin.BoneMass)/1000.0, "kg")
- t.AddValueUnit("Muscle Mass", float64(weightin.MuscleMass)/1000.0, "kg")
- t.Output(os.Stdout)
-}
-
-func weightLatestWeek(_ *cobra.Command, _ []string) {
- now := time.Now()
- from := time.Now().Add(-24 * 6 * time.Hour)
-
- average, _, err := client.Weightins(from, now)
- bail(err)
-
- t := NewTabular()
- t.AddValue("Average from", formatDate(from))
- t.AddValueUnit("Weight", average.Weight/1000.0, "kg")
- t.AddValueUnit("BMI", average.BMI, "kg/m2")
- t.AddValueUnit("Fat", average.BodyFatPercentage, "%")
- t.AddValueUnit("Fat Mass", (average.Weight*average.BodyFatPercentage)/100000.0, "kg")
- t.AddValueUnit("Water", average.BodyWater, "%")
- t.AddValueUnit("Bone Mass", float64(average.BoneMass)/1000.0, "kg")
- t.AddValueUnit("Muscle Mass", float64(average.MuscleMass)/1000.0, "kg")
- t.Output(os.Stdout)
-}
-
-func weightAdd(_ *cobra.Command, args []string) {
- date, err := connect.ParseDate(args[0])
- bail(err)
-
- weight, err := strconv.Atoi(args[1])
- bail(err)
-
- err = client.AddUserWeight(date.Time(), float64(weight))
- bail(err)
-}
-
-func weightDelete(_ *cobra.Command, args []string) {
- date, err := connect.ParseDate(args[0])
- bail(err)
-
- err = client.DeleteWeightin(date.Time())
- bail(err)
-}
-
-func weightDate(_ *cobra.Command, args []string) {
- date, err := connect.ParseDate(args[0])
- bail(err)
-
- tim, weight, err := client.WeightByDate(date.Time())
- bail(err)
-
- zero := time.Time{}
- if tim.Time == zero {
- fmt.Printf("No weight ins on this date\n")
- os.Exit(1)
- }
-
- t := NewTabular()
- t.AddValue("Time", tim.String())
- t.AddValueUnit("Weight", weight/1000.0, "kg")
- t.Output(os.Stdout)
-}
-
-func weightRange(_ *cobra.Command, args []string) {
- from, err := connect.ParseDate(args[0])
- bail(err)
-
- to, err := connect.ParseDate(args[1])
- bail(err)
-
- average, weightins, err := client.Weightins(from.Time(), to.Time())
- bail(err)
-
- t := NewTabular()
-
- t.AddValueUnit("Weight", average.Weight/1000.0, "kg")
- t.AddValueUnit("BMI", average.BMI, "kg/m2")
- t.AddValueUnit("Fat", average.BodyFatPercentage, "%")
- t.AddValueUnit("Fat Mass", average.Weight*average.BodyFatPercentage/100000.0, "kg")
- t.AddValueUnit("Water", average.BodyWater, "%")
- t.AddValueUnit("Bone Mass", float64(average.BoneMass)/1000.0, "kg")
- t.AddValueUnit("Muscle Mass", float64(average.MuscleMass)/1000.0, "kg")
- fmt.Fprintf(os.Stdout, " \033[1mAverage\033[0m\n")
- t.Output(os.Stdout)
-
- t2 := NewTable()
- t2.AddHeader("Date", "Weight", "BMI", "Fat%", "Fat", "Water%", "Bone Mass", "Muscle Mass")
- for _, weightin := range weightins {
- if weightin.Weight < 1.0 {
- continue
- }
-
- t2.AddRow(
- weightin.Date,
- weightin.Weight/1000.0,
- nzf(weightin.BMI),
- nzf(weightin.BodyFatPercentage),
- nzf(weightin.Weight*weightin.BodyFatPercentage/100000.0),
- nzf(weightin.BodyWater),
- nzf(float64(weightin.BoneMass)/1000.0),
- nzf(float64(weightin.MuscleMass)/1000.0),
- )
- }
- fmt.Fprintf(os.Stdout, "\n")
- t2.Output(os.Stdout)
-}
-
-func weightGoal(_ *cobra.Command, args []string) {
- displayName := ""
-
- if len(args) > 0 {
- displayName = args[0]
- }
-
- goal, err := client.WeightGoal(displayName)
- bail(err)
-
- t := NewTabular()
- t.AddValue("ID", goal.ID)
- t.AddValue("Created", goal.Created)
- t.AddValueUnit("Target", float64(goal.Value)/1000.0, "kg")
- t.Output(os.Stdout)
-}
-
-func weightGoalSet(_ *cobra.Command, args []string) {
- goal, err := strconv.Atoi(args[0])
- bail(err)
-
- err = client.SetWeightGoal(goal)
- bail(err)
-}
diff --git a/python-garmin-connect/doc.go b/python-garmin-connect/doc.go
deleted file mode 100644
index fdc42fc..0000000
--- a/python-garmin-connect/doc.go
+++ /dev/null
@@ -1,4 +0,0 @@
-// Package connect provides access to the unofficial Garmin Connect API. This
-// is not supported or endorsed by Garmin Ltd. The API may change or stop
-// working at any time. Please use responsible.
-package connect
diff --git a/python-garmin-connect/go.mod b/python-garmin-connect/go.mod
deleted file mode 100644
index b4831ad..0000000
--- a/python-garmin-connect/go.mod
+++ /dev/null
@@ -1,8 +0,0 @@
-module github.com/abrander/garmin-connect
-
-go 1.15
-
-require (
- github.com/spf13/cobra v1.1.1
- golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
-)
diff --git a/python-garmin-connect/go.sum b/python-garmin-connect/go.sum
deleted file mode 100644
index b346227..0000000
--- a/python-garmin-connect/go.sum
+++ /dev/null
@@ -1,292 +0,0 @@
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
-cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
-cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
-cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
-cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
-github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
-github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
-github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
-github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
-github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
-github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
-github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
-github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
-github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
-github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
-github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
-github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
-github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
-github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
-github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
-github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
-github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
-github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
-github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
-github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
-github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
-github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
-github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
-github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
-github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
-github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
-github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
-github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
-github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
-github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
-github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
-github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
-github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
-github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
-github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
-github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
-github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
-go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
-go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
-golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
-golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
-golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
-google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
-google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
-gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
diff --git a/python-garmin-connect/tools.go b/python-garmin-connect/tools.go
deleted file mode 100644
index 3d6d9c0..0000000
--- a/python-garmin-connect/tools.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package connect
-
-import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "time"
-)
-
-// date formats a time.Time as a date usable in the Garmin Connect API.
-func formatDate(t time.Time) string {
- return fmt.Sprintf("%04d-%02d-%02d", t.Year(), t.Month(), t.Day())
-}
-
-// drainBody reads all of b to memory and then returns two equivalent
-// ReadClosers yielding the same bytes.
-//
-// It returns an error if the initial slurp of all bytes fails. It does not attempt
-// to make the returned ReadClosers have identical error-matching behavior.
-//
-// Liberated from net/http/httputil/dump.go.
-func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
- if b == http.NoBody {
- // No copying needed. Preserve the magic sentinel meaning of NoBody.
- return http.NoBody, http.NoBody, nil
- }
- var buf bytes.Buffer
- if _, err = buf.ReadFrom(b); err != nil {
- return nil, b, err
- }
- if err = b.Close(); err != nil {
- return nil, b, err
- }
- return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil
-}