├── .gitattributes ├── .gitignore ├── GarminConnectClient.Console ├── ConsoleLogger.cs ├── GarminConnectClient.Console.csproj ├── Program.cs ├── appsettings.json └── appsettings.local.json ├── GarminConnectClient.Lib ├── Configuration.cs ├── Dto │ ├── AccessControlRule.cs │ ├── Activity.cs │ ├── ActivityImage.cs │ ├── ActivityType.cs │ ├── ChartAvailability.cs │ ├── DeviceMetaData.cs │ ├── FileFormat.cs │ ├── Metadata.cs │ ├── Sensor.cs │ ├── Summary.cs │ ├── TimeZoneUnit.cs │ └── UserInfo.cs ├── Enum │ ├── ActivityFileTypeEnum.cs │ ├── ActivityTypeEnum.cs │ └── EventTypeEnum.cs ├── GarminConnectClient.Lib.csproj ├── GarminConnectClient.Lib.xml ├── IConfiguration.cs ├── Properties │ └── PublishProfiles │ │ └── FolderProfile.pubxml ├── Services │ ├── Client.cs │ ├── CloudStorage.cs │ ├── Downloader.cs │ ├── IClient.cs │ ├── IDownloader.cs │ └── IStorage.cs └── scripts │ └── build_and_publish.ps1 ├── GarminConnectClient.sln ├── GarminConnectClient.sln.DotSettings ├── LICENSE.md ├── README.md ├── logo-128.png ├── logo-256.png └── logo.png /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #Ignore thumbnails created by Windows 3 | Thumbs.db 4 | #Ignore files built by Visual Studio 5 | *.obj 6 | *.exe 7 | *.pdb 8 | *.user 9 | *.aps 10 | *.pch 11 | *.vspscc 12 | *_i.c 13 | *_p.c 14 | *.ncb 15 | *.suo 16 | *.tlb 17 | *.tlh 18 | *.bak 19 | *.cache 20 | *.ilk 21 | *.log 22 | [Bb]in 23 | [Dd]ebug*/ 24 | *.lib 25 | *.sbr 26 | obj/ 27 | [Rr]elease*/ 28 | _ReSharper*/ 29 | [Tt]est[Rr]esult* 30 | .vs/ 31 | #Nuget packages folder 32 | packages/ 33 | !GarminConnectClient.Lib/ 34 | /GarminConnectClient.Console/appsettings.local.json 35 | /.idea/ 36 | -------------------------------------------------------------------------------- /GarminConnectClient.Console/ConsoleLogger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | namespace GarminConnectClient.Console 6 | { 7 | [SuppressMessage("ReSharper", "UnusedMember.Global")] 8 | public class ConsoleLogger : ILogger 9 | { 10 | public void Error(string message) 11 | { 12 | System.Console.WriteLine($"Error: {message}"); 13 | } 14 | 15 | public void Information(string message) 16 | { 17 | System.Console.WriteLine($"Information: {message}"); 18 | } 19 | 20 | public void Warning(string message) 21 | { 22 | System.Console.WriteLine($"warning: {message}"); 23 | } 24 | 25 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 26 | { 27 | System.Console.WriteLine(state.ToString()); 28 | } 29 | 30 | public bool IsEnabled(LogLevel logLevel) 31 | { 32 | throw new NotImplementedException(); 33 | } 34 | 35 | public IDisposable BeginScope(TState state) 36 | { 37 | throw new NotImplementedException(); 38 | } 39 | } 40 | 41 | [SuppressMessage("ReSharper", "UnusedMember.Global")] 42 | public class ConsoleLogger : ILogger 43 | { 44 | public void Error(string message) 45 | { 46 | System.Console.WriteLine($"Error: {message}"); 47 | } 48 | 49 | public void Information(string message) 50 | { 51 | System.Console.WriteLine($"Information: {message}"); 52 | } 53 | 54 | public void Warning(string message) 55 | { 56 | System.Console.WriteLine($"warning: {message}"); 57 | } 58 | 59 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 60 | { 61 | System.Console.WriteLine(state.ToString()); 62 | } 63 | 64 | public bool IsEnabled(LogLevel logLevel) 65 | { 66 | throw new NotImplementedException(); 67 | } 68 | 69 | public IDisposable BeginScope(TState state) 70 | { 71 | throw new NotImplementedException(); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /GarminConnectClient.Console/GarminConnectClient.Console.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | 1.2.0 7 | Marazt 8 | Marazt 9 | A test and usage example project. 10 | © 2020 Marek Polak 11 | https://github.com/marazt/garmin-connect-client 12 | https://github.com/marazt/garmin-connect-client 13 | git 14 | garmin garminconnect backup activity 15 | Fix of the new GarminConnect login workflow. 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Always 26 | 27 | 28 | Always 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /GarminConnectClient.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using GarminConnectClient.Lib; 2 | using GarminConnectClient.Lib.Dto; 3 | using GarminConnectClient.Lib.Enum; 4 | using GarminConnectClient.Lib.Services; 5 | using Microsoft.Extensions.Configuration; 6 | using MovescountBackup.Lib.Dto; 7 | using MovescountBackup.Lib.Enums; 8 | using Newtonsoft.Json; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.IO; 12 | using System.Linq; 13 | using System.Text; 14 | using System.Threading.Tasks; 15 | 16 | // ReSharper disable RedundantCaseLabel 17 | 18 | namespace GarminConnectClient.Console 19 | { 20 | // ReSharper disable once ArrangeTypeModifiers 21 | // ReSharper disable once ClassNeverInstantiated.Global 22 | internal class Program 23 | { 24 | private const int Timeout = 20000; 25 | 26 | 27 | // ReSharper disable once ArrangeTypeMemberModifiers 28 | // ReSharper disable once UnusedParameter.Local 29 | #pragma warning disable IDE0060 // Remove unused parameter 30 | private static void Main(string[] args) 31 | #pragma warning restore IDE0060 // Remove unused parameter 32 | { 33 | var storedFiles = new Dictionary(); 34 | using (var stream = new FileStream(@"C:\Users\maraz\Desktop\moves\log.txt", FileMode.OpenOrCreate)) 35 | { 36 | using (var logFile = new StreamReader(stream)) 37 | { 38 | var records = logFile.ReadToEnd().Split(Environment.NewLine); 39 | if (records.Length > 1) 40 | { 41 | storedFiles = records.Where(e => e.Contains("|")).Select(e => 42 | { 43 | var parts = e.Split("|"); 44 | return new { Key = parts[0], Value = parts[1] }; 45 | }) 46 | .ToDictionary(e => e.Key, e => e.Value); 47 | } 48 | } 49 | } 50 | 51 | var configuration = SetupConfiguration(); 52 | var client = new Client(configuration, new ConsoleLogger()); 53 | 54 | System.Console.WriteLine("Loading event types"); 55 | 56 | client.Authenticate().Wait(); 57 | var eventTypes = client.LoadEventTypes().Result; 58 | 59 | Task.Delay(Timeout).Wait(); 60 | 61 | System.Console.WriteLine("Loading activity types"); 62 | 63 | var activityTypes = client.LoadActivityTypes().Result; 64 | 65 | System.Console.WriteLine("-------------------------------------------------------------------------------"); 66 | 67 | var allGpxFiles = Directory.GetFiles(@"C:\Users\maraz\Desktop\moves", "*.gpx*", SearchOption.AllDirectories); 68 | foreach (var gpxFile in allGpxFiles) 69 | { 70 | Move activityData = null; 71 | 72 | try 73 | { 74 | var jsonDataFile = gpxFile.Replace("gps_data.gpx", "move_data.json"); 75 | 76 | using (var jsonDataStream = new StreamReader(jsonDataFile)) 77 | { 78 | activityData = JsonConvert.DeserializeObject(jsonDataStream.ReadToEnd()); 79 | } 80 | 81 | if (storedFiles.ContainsKey(activityData.MoveId.ToString())) 82 | { 83 | System.Console.WriteLine($"Garmin Connect move {activityData.MoveId} (Garmin activity {storedFiles[activityData.MoveId.ToString()]}) should be already stored or upload failed."); 84 | System.Console.WriteLine("-------------------------------------------------------------------------------"); 85 | continue; 86 | } 87 | 88 | System.Console.WriteLine($"Uploading Garmin Connect move {activityData.MoveId}"); 89 | var (Success, ActivityId) = client.UploadActivity(gpxFile, new FileFormat { FormatKey = "gpx" }).Result; 90 | if (!Success) 91 | { 92 | System.Console.WriteLine($"Error while uploading uploading Garmin Connect move {activityData.MoveId}."); 93 | throw new Exception("Error while uploading uploading Garmin Connect move {activityData.MoveId}."); 94 | } 95 | 96 | var name = activityData.Notes != null 97 | ? activityData.Notes.Split('.').FirstOrDefault() ?? activityData.MoveId.ToString() 98 | : activityData.MoveId.ToString(); 99 | 100 | System.Console.WriteLine($"Setting name of Garmin Connect move {activityData.MoveId} (Garmin activity {ActivityId}) to '{name}'."); 101 | client.SetActivityName(ActivityId, name).Wait(); 102 | 103 | Task.Delay(Timeout).Wait(); 104 | 105 | var description = CreateDescription(activityData); 106 | 107 | System.Console.WriteLine($"Setting description of Garmin Connect move {activityData.MoveId} (Garmin activity {ActivityId}) to '{description}'."); 108 | client.SetActivityDescription(ActivityId, description).Wait(); 109 | 110 | Task.Delay(Timeout).Wait(); 111 | 112 | var activityType = activityTypes.FirstOrDefault(e => string.Equals(e.TypeKey, ResolveActivityType(activityData.ActivityID).TypeKey, StringComparison.InvariantCultureIgnoreCase)); 113 | if (activityType != null) 114 | { 115 | System.Console.WriteLine($"Setting activity type of Garmin Connect move {activityData.MoveId} (Garmin activity {ActivityId}) to '{activityType.TypeKey}'."); 116 | client.SetActivityType(ActivityId, activityType).Wait(); 117 | Task.Delay(Timeout).Wait(); 118 | } 119 | 120 | var eventType = eventTypes.FirstOrDefault(e => string.Equals(e.TypeKey, activityType != null && activityType.TypeId == (int)ActivityTypeEnum.Running 121 | ? EventTypeEnum.Training.ToString() 122 | : EventTypeEnum.Fitness.ToString(), StringComparison.InvariantCultureIgnoreCase)); 123 | 124 | if (eventType != null) 125 | { 126 | System.Console.WriteLine($"Setting event type of Garmin Connect move {activityData.MoveId} (Garmin activity {ActivityId}) to '{eventType.TypeKey}'."); 127 | client.SetEventType(ActivityId, eventType).Wait(); 128 | Task.Delay(Timeout).Wait(); 129 | } 130 | 131 | storedFiles.Add(activityData.MoveId.ToString(), ActivityId.ToString()); 132 | UpdateLogFile(activityData.MoveId, ActivityId); 133 | 134 | System.Console.WriteLine("-------------------------------------------------------------------------------"); 135 | } 136 | catch (Exception ex) 137 | { 138 | System.Console.WriteLine($"Error while uploading Garmin Connect move {activityData?.MoveId}: {ex.Message}."); 139 | System.Console.WriteLine($"Error while uploading move: {ex.StackTrace}."); 140 | storedFiles.Add(activityData.MoveId.ToString(), "0"); 141 | UpdateLogFile(activityData.MoveId, 0); 142 | } 143 | } 144 | } 145 | 146 | private static void UpdateLogFile(int moveId, long activityId) 147 | { 148 | using (var stream = new FileStream(@"C:\Users\maraz\Desktop\moves\log.txt", FileMode.Append)) 149 | { 150 | System.Console.WriteLine($"Updating log file of move {moveId} (Garmin activity {activityId})."); 151 | using (var logFile = new StreamWriter(stream)) 152 | { 153 | logFile.WriteLine($"{moveId}|{activityId}"); 154 | } 155 | } 156 | } 157 | 158 | private static ActivityType ResolveActivityType(ActivityIdEnum activityId) 159 | { 160 | switch (activityId) 161 | { 162 | case ActivityIdEnum.CircuitTraining: 163 | return new ActivityType { TypeKey = "indoor_cardio" }; 164 | 165 | case ActivityIdEnum.Climbing: 166 | return new ActivityType { TypeKey = "rock_climbing" }; 167 | 168 | case ActivityIdEnum.CrossFit: 169 | return new ActivityType { TypeKey = "strength_training" }; 170 | 171 | case ActivityIdEnum.Cycling: 172 | return new ActivityType { TypeKey = "cycling" }; 173 | 174 | case ActivityIdEnum.MultiSport: 175 | return new ActivityType { TypeKey = "multi_sport" }; 176 | 177 | case ActivityIdEnum.NotSpecifiedSport: 178 | return new ActivityType { TypeKey = "uncategorized" }; 179 | 180 | case ActivityIdEnum.Run: 181 | return new ActivityType { TypeKey = "running" }; 182 | 183 | case ActivityIdEnum.Walking: 184 | return new ActivityType { TypeKey = "walking" }; 185 | 186 | case ActivityIdEnum.NordicWalking: 187 | case ActivityIdEnum.Trekking: 188 | return new ActivityType { TypeKey = "walking" }; 189 | 190 | case ActivityIdEnum.Swimming: 191 | return new ActivityType { TypeKey = "swimming" }; 192 | 193 | case ActivityIdEnum.OpenWaterSwimming: 194 | return new ActivityType { TypeKey = "open_water_swimming" }; 195 | 196 | case ActivityIdEnum.Skating: 197 | return new ActivityType { TypeKey = "inline_skating" }; 198 | 199 | case ActivityIdEnum.IceSkating: 200 | return new ActivityType { TypeKey = "skating" }; 201 | 202 | case ActivityIdEnum.CrosscountrySkiing: 203 | return new ActivityType { TypeKey = "cross_country_skiing" }; 204 | 205 | case ActivityIdEnum.AlpineSkiing: 206 | return new ActivityType { TypeKey = "skate_skiing" }; 207 | 208 | case ActivityIdEnum.IndoorTraining: 209 | return new ActivityType { TypeKey = "indoor_cardio" }; 210 | 211 | case ActivityIdEnum.TrailRunning: 212 | return new ActivityType { TypeKey = "trail_running" }; 213 | 214 | case ActivityIdEnum.MountainBiking: 215 | case ActivityIdEnum.Aerobics: 216 | case ActivityIdEnum.YogaPilates: 217 | case ActivityIdEnum.Sailing: 218 | case ActivityIdEnum.Kayaking: 219 | case ActivityIdEnum.Rowing: 220 | case ActivityIdEnum.IndoorCycling: 221 | case ActivityIdEnum.Triathlon: 222 | case ActivityIdEnum.Snowboarding: 223 | case ActivityIdEnum.WeightTraining: 224 | case ActivityIdEnum.Basketball: 225 | case ActivityIdEnum.Soccer: 226 | case ActivityIdEnum.IceHockey: 227 | case ActivityIdEnum.Volleyball: 228 | case ActivityIdEnum.Football: 229 | case ActivityIdEnum.Softball: 230 | case ActivityIdEnum.Cheerleading: 231 | case ActivityIdEnum.Baseball: 232 | case ActivityIdEnum.Tennis: 233 | case ActivityIdEnum.Badminton: 234 | case ActivityIdEnum.TableTennis: 235 | case ActivityIdEnum.RacquetBall: 236 | case ActivityIdEnum.Squash: 237 | case ActivityIdEnum.CombatSport: 238 | case ActivityIdEnum.Boxing: 239 | case ActivityIdEnum.Floorball: 240 | case ActivityIdEnum.ScubaDiving: 241 | case ActivityIdEnum.FreeDiving: 242 | case ActivityIdEnum.AdventureRacing: 243 | case ActivityIdEnum.Bowling: 244 | case ActivityIdEnum.Cricket: 245 | case ActivityIdEnum.Crosstrainer: 246 | case ActivityIdEnum.Dancing: 247 | case ActivityIdEnum.Golf: 248 | case ActivityIdEnum.Gymnastics: 249 | case ActivityIdEnum.Handball: 250 | case ActivityIdEnum.HorsebackRiding: 251 | case ActivityIdEnum.IndoorRowing: 252 | case ActivityIdEnum.Canoeing: 253 | case ActivityIdEnum.Motorsports: 254 | case ActivityIdEnum.Mountaineering: 255 | case ActivityIdEnum.Orienteering: 256 | case ActivityIdEnum.Rugby: 257 | case ActivityIdEnum.SkiTouring: 258 | case ActivityIdEnum.Stretching: 259 | case ActivityIdEnum.TelemarkSkiing: 260 | case ActivityIdEnum.TrackAndField: 261 | case ActivityIdEnum.SnowShoeing: 262 | case ActivityIdEnum.Surfing: 263 | case ActivityIdEnum.Kettlebell: 264 | case ActivityIdEnum.RollerSkiing: 265 | case ActivityIdEnum.StandupPaddling: 266 | case ActivityIdEnum.Kiting: 267 | case ActivityIdEnum.Paragliding: 268 | case ActivityIdEnum.Treadmill: 269 | case ActivityIdEnum.Frisbee: 270 | default: 271 | return new ActivityType { TypeKey = "uncategorized" }; 272 | } 273 | } 274 | 275 | private static string CreateDescription(Move move) 276 | { 277 | var notes = new StringBuilder(string.Empty); 278 | notes.AppendLine("Garmin Connect data:"); 279 | notes.AppendLine($"Move: http://www.movescount.com/moves/move{move.MoveId}"); 280 | notes.AppendLine( 281 | $"Feeling: {(move.Feeling.HasValue ? Enum.Parse(typeof(FeelingEnum), move.Feeling.ToString()).ToString() : "-")}"); 282 | notes.AppendLine( 283 | $"Weather: {(move.Weather.HasValue ? Enum.Parse(typeof(WeatherEnum), move.Weather.ToString()).ToString() : "-")}"); 284 | notes.AppendLine($"Tags: {move.Tags}"); 285 | notes.AppendLine( 286 | $"RecoveryTime: {(move.RecoveryTime.HasValue ? $"{Math.Round(move.RecoveryTime.Value / 3600, 1)}h" : "-")}"); 287 | 288 | return $"{move.Notes?.Replace(" + ", " plus ")}\n{notes}"; 289 | } 290 | 291 | private static Lib.IConfiguration SetupConfiguration() 292 | { 293 | var builder = new ConfigurationBuilder() 294 | .SetBasePath(Directory.GetCurrentDirectory()) 295 | .AddJsonFile("appsettings.json", false, true) 296 | .AddEnvironmentVariables(); 297 | var configuration = builder.Build(); 298 | return new Configuration(configuration); 299 | } 300 | } 301 | } -------------------------------------------------------------------------------- /GarminConnectClient.Console/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Warning" 6 | } 7 | }, 8 | "AppConfig": { 9 | "Username": "SET_ME", 10 | "Password": "SET_ME", 11 | "BackupDir": "SET_ME", 12 | "StorageConnectionString": "SET_ME", 13 | "ContainerName": "SET_ME" 14 | } 15 | } -------------------------------------------------------------------------------- /GarminConnectClient.Console/appsettings.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Warning" 6 | } 7 | }, 8 | "AppConfig": { 9 | "Username": "SET_ME", 10 | "Password": "SET_ME", 11 | "BackupDir": "SET_ME", 12 | "BackupDirOsx": "SET_ME", 13 | "BackupDirWin": "SET_ME", 14 | "StravaAccessToken": "SET_ME", 15 | "StorageConnectionString": "SET_ME", 16 | "ContainerName": "SET_ME" 17 | } 18 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Configuration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace GarminConnectClient.Lib 4 | { 5 | /// 6 | /// 7 | /// Configuration 8 | /// 9 | /// 10 | public class Configuration : IConfiguration 11 | { 12 | // ReSharper disable once InconsistentNaming 13 | private readonly IConfigurationRoot root; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The root. 19 | public Configuration(IConfigurationRoot root) 20 | { 21 | this.root = root; 22 | } 23 | 24 | /// 25 | /// 26 | /// Gets the username. 27 | /// 28 | /// 29 | /// The username. 30 | /// 31 | public string Username => this.root["AppConfig:Username"]; 32 | 33 | /// 34 | /// 35 | /// Gets the password. 36 | /// 37 | /// 38 | /// The password. 39 | /// 40 | public string Password => this.root["AppConfig:Password"]; 41 | 42 | /// 43 | /// 44 | /// Gets the backup dir. 45 | /// 46 | /// 47 | /// The backup dir. 48 | /// 49 | public string BackupDir => this.root["AppConfig:BackupDir"]; 50 | 51 | /// 52 | /// 53 | /// Gets the storage connection string. 54 | /// 55 | /// 56 | /// The storage connection string. 57 | /// 58 | public string StorageConnectionString => this.root["AppConfig:StorageConnectionString"]; 59 | 60 | /// 61 | /// 62 | /// Gets the name of the container. 63 | /// 64 | /// 65 | /// The name of the container. 66 | /// 67 | public string ContainerName => this.root["AppConfig:ContainerName"]; 68 | } 69 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Dto/AccessControlRule.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace GarminConnectClient.Lib.Dto 4 | { 5 | /// 6 | /// Access control rule 7 | /// 8 | public class AccessControlRule 9 | { 10 | /// 11 | /// Gets or sets the type identifier. 12 | /// 13 | /// 14 | /// The type identifier. 15 | /// 16 | [JsonProperty("typeId")] 17 | public long TypeId { get; set; } 18 | 19 | /// 20 | /// Gets or sets the type key. 21 | /// 22 | /// 23 | /// The type key. 24 | /// 25 | [JsonProperty("typeKey")] 26 | public string TypeKey { get; set; } 27 | } 28 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Dto/Activity.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace GarminConnectClient.Lib.Dto 4 | { 5 | /// 6 | /// Activity 7 | /// 8 | public class Activity 9 | { 10 | /// 11 | /// Gets or sets the activity identifier. 12 | /// 13 | /// 14 | /// The activity identifier. 15 | /// 16 | [JsonProperty("activityId")] 17 | public long ActivityId { get; set; } 18 | 19 | /// 20 | /// Gets or sets the name of the activity. 21 | /// 22 | /// 23 | /// The name of the activity. 24 | /// 25 | [JsonProperty("activityName")] 26 | public string ActivityName { get; set; } 27 | 28 | /// 29 | /// Gets or sets the description. 30 | /// 31 | /// 32 | /// The description. 33 | /// 34 | [JsonProperty("description")] 35 | public string Description { get; set; } 36 | 37 | /// 38 | /// Gets or sets the user profile identifier. 39 | /// 40 | /// 41 | /// The user profile identifier. 42 | /// 43 | [JsonProperty("userProfileId")] 44 | public long UserProfileId { get; set; } 45 | 46 | /// 47 | /// Gets or sets a value indicating whether this instance is multi sport parent. 48 | /// 49 | /// 50 | /// true if this instance is multi sport parent; otherwise, false. 51 | /// 52 | [JsonProperty("isMultiSportParent")] 53 | public bool IsMultiSportParent { get; set; } 54 | 55 | /// 56 | /// Gets or sets the type of the activity. 57 | /// 58 | /// 59 | /// The type of the activity. 60 | /// 61 | [JsonProperty("activityTypeDTO")] 62 | public ActivityType ActivityType { get; set; } 63 | 64 | 65 | /// 66 | /// Gets or sets the type of the activity. 67 | /// Overloaded JsonProperty for the same property - "activityTypeDTO". 68 | /// 69 | /// 70 | /// The type of the activity. 71 | /// 72 | [JsonProperty("activityType")] 73 | #pragma warning disable IDE0051 // Remove unused private members 74 | private ActivityType ActivityTypeInternal { set { this.ActivityType = value; } } 75 | #pragma warning restore IDE0051 // Remove unused private members 76 | 77 | /// 78 | /// Gets or sets the type of the event. 79 | /// 80 | /// 81 | /// The type of the event. 82 | /// 83 | [JsonProperty("eventTypeDTO")] 84 | public ActivityType EventType { get; set; } 85 | 86 | /// 87 | /// Gets or sets the access control rule. 88 | /// 89 | /// 90 | /// The access control rule. 91 | /// 92 | [JsonProperty("accessControlRuleDTO")] 93 | public AccessControlRule AccessControlRule { get; set; } 94 | 95 | /// 96 | /// Gets or sets the time zone unit. 97 | /// 98 | /// 99 | /// The time zone unit. 100 | /// 101 | [JsonProperty("timeZoneUnitDTO")] 102 | public TimeZoneUnit TimeZoneUnit { get; set; } 103 | 104 | /// 105 | /// Gets or sets the metadata. 106 | /// 107 | /// 108 | /// The metadata. 109 | /// 110 | [JsonProperty("metadataDTO")] 111 | public Metadata Metadata { get; set; } 112 | 113 | /// 114 | /// Gets or sets the summary. 115 | /// 116 | /// 117 | /// The summary. 118 | /// 119 | [JsonProperty("summaryDTO")] 120 | public Summary Summary { get; set; } 121 | 122 | /// 123 | /// Gets or sets the name of the location. 124 | /// 125 | /// 126 | /// The name of the location. 127 | /// 128 | [JsonProperty("locationName")] 129 | public string LocationName { get; set; } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Dto/ActivityImage.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace GarminConnectClient.Lib.Dto 5 | { 6 | /// 7 | /// Activity image 8 | /// 9 | public class ActivityImage 10 | { 11 | /// 12 | /// Gets or sets the image identifier. 13 | /// 14 | /// 15 | /// The image identifier. 16 | /// 17 | [JsonProperty("imageId")] 18 | public string ImageId { get; set; } 19 | 20 | /// 21 | /// Gets or sets the URL. 22 | /// 23 | /// 24 | /// The URL. 25 | /// 26 | [JsonProperty("url")] 27 | public Uri Url { get; set; } 28 | 29 | /// 30 | /// Gets or sets the small URL. 31 | /// 32 | /// 33 | /// The small URL. 34 | /// 35 | [JsonProperty("smallUrl")] 36 | public Uri SmallUrl { get; set; } 37 | 38 | /// 39 | /// Gets or sets the medium URL. 40 | /// 41 | /// 42 | /// The medium URL. 43 | /// 44 | [JsonProperty("mediumUrl")] 45 | public Uri MediumUrl { get; set; } 46 | 47 | /// 48 | /// Gets or sets the latitude. 49 | /// 50 | /// 51 | /// The latitude. 52 | /// 53 | [JsonProperty("latitude")] 54 | public object Latitude { get; set; } 55 | 56 | /// 57 | /// Gets or sets the longitude. 58 | /// 59 | /// 60 | /// The longitude. 61 | /// 62 | [JsonProperty("longitude")] 63 | public object Longitude { get; set; } 64 | 65 | /// 66 | /// Gets or sets the photo date. 67 | /// 68 | /// 69 | /// The photo date. 70 | /// 71 | [JsonProperty("photoDate")] 72 | public DateTimeOffset PhotoDate { get; set; } 73 | } 74 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Dto/ActivityType.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace GarminConnectClient.Lib.Dto 4 | { 5 | /// 6 | /// Activity type 7 | /// 8 | public class ActivityType 9 | { 10 | /// 11 | /// Gets or sets the type identifier. 12 | /// 13 | /// 14 | /// The type identifier. 15 | /// 16 | [JsonProperty("typeId")] 17 | public long TypeId { get; set; } 18 | 19 | /// 20 | /// Gets or sets the type key. 21 | /// 22 | /// 23 | /// The type key. 24 | /// 25 | [JsonProperty("typeKey")] 26 | public string TypeKey { get; set; } 27 | 28 | /// 29 | /// Gets or sets the parent type identifier. 30 | /// 31 | /// 32 | /// The parent type identifier. 33 | /// 34 | [JsonProperty("parentTypeId", NullValueHandling = NullValueHandling.Ignore)] 35 | public long? ParentTypeId { get; set; } 36 | 37 | /// 38 | /// Gets or sets the sort order. 39 | /// 40 | /// 41 | /// The sort order. 42 | /// 43 | [JsonProperty("sortOrder")] 44 | public long SortOrder { get; set; } 45 | } 46 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Dto/ChartAvailability.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace GarminConnectClient.Lib.Dto 4 | { 5 | /// 6 | /// Chart availability 7 | /// 8 | public class ChartAvailability 9 | { 10 | /// 11 | /// Gets or sets a value indicating whether [show air temperature]. 12 | /// 13 | /// 14 | /// true if [show air temperature]; otherwise, false. 15 | /// 16 | [JsonProperty("showAirTemperature")] 17 | public bool ShowAirTemperature { get; set; } 18 | 19 | /// 20 | /// Gets or sets a value indicating whether [show distance]. 21 | /// 22 | /// 23 | /// true if [show distance]; otherwise, false. 24 | /// 25 | [JsonProperty("showDistance")] 26 | public bool ShowDistance { get; set; } 27 | 28 | /// 29 | /// Gets or sets a value indicating whether [show duration]. 30 | /// 31 | /// 32 | /// true if [show duration]; otherwise, false. 33 | /// 34 | [JsonProperty("showDuration")] 35 | public bool ShowDuration { get; set; } 36 | 37 | /// 38 | /// Gets or sets a value indicating whether [show elevation]. 39 | /// 40 | /// 41 | /// true if [show elevation]; otherwise, false. 42 | /// 43 | [JsonProperty("showElevation")] 44 | public bool ShowElevation { get; set; } 45 | 46 | /// 47 | /// Gets or sets a value indicating whether [show heart rate]. 48 | /// 49 | /// 50 | /// true if [show heart rate]; otherwise, false. 51 | /// 52 | [JsonProperty("showHeartRate")] 53 | public bool ShowHeartRate { get; set; } 54 | 55 | /// 56 | /// Gets or sets a value indicating whether [show moving duration]. 57 | /// 58 | /// 59 | /// true if [show moving duration]; otherwise, false. 60 | /// 61 | [JsonProperty("showMovingDuration")] 62 | public bool ShowMovingDuration { get; set; } 63 | 64 | /// 65 | /// Gets or sets a value indicating whether [show moving speed]. 66 | /// 67 | /// 68 | /// true if [show moving speed]; otherwise, false. 69 | /// 70 | [JsonProperty("showMovingSpeed")] 71 | public bool ShowMovingSpeed { get; set; } 72 | 73 | /// 74 | /// Gets or sets a value indicating whether [show run cadence]. 75 | /// 76 | /// 77 | /// true if [show run cadence]; otherwise, false. 78 | /// 79 | [JsonProperty("showRunCadence")] 80 | public bool ShowRunCadence { get; set; } 81 | 82 | /// 83 | /// Gets or sets a value indicating whether [show speed]. 84 | /// 85 | /// 86 | /// true if [show speed]; otherwise, false. 87 | /// 88 | [JsonProperty("showSpeed")] 89 | public bool ShowSpeed { get; set; } 90 | 91 | /// 92 | /// Gets or sets a value indicating whether [show timestamp]. 93 | /// 94 | /// 95 | /// true if [show timestamp]; otherwise, false. 96 | /// 97 | [JsonProperty("showTimestamp")] 98 | public bool ShowTimestamp { get; set; } 99 | } 100 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Dto/DeviceMetaData.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace GarminConnectClient.Lib.Dto 4 | { 5 | /// 6 | /// Device metadata 7 | /// 8 | public class DeviceMetaData 9 | { 10 | /// 11 | /// Gets or sets the device identifier. 12 | /// 13 | /// 14 | /// The device identifier. 15 | /// 16 | [JsonProperty("deviceId")] 17 | public string DeviceId { get; set; } 18 | 19 | /// 20 | /// Gets or sets the device type pk. 21 | /// 22 | /// 23 | /// The device type pk. 24 | /// 25 | [JsonProperty("deviceTypePk")] 26 | public long DeviceTypePk { get; set; } 27 | 28 | /// 29 | /// Gets or sets the device version pk. 30 | /// 31 | /// 32 | /// The device version pk. 33 | /// 34 | [JsonProperty("deviceVersionPk")] 35 | public long DeviceVersionPk { get; set; } 36 | } 37 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Dto/FileFormat.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace GarminConnectClient.Lib.Dto 4 | { 5 | /// 6 | /// File format 7 | /// 8 | public class FileFormat 9 | { 10 | /// 11 | /// Gets or sets the format identifier. 12 | /// 13 | /// 14 | /// The format identifier. 15 | /// 16 | [JsonProperty("formatId")] 17 | public long FormatId { get; set; } 18 | 19 | /// 20 | /// Gets or sets the format key. 21 | /// 22 | /// 23 | /// The format key. 24 | /// 25 | [JsonProperty("formatKey")] 26 | public string FormatKey { get; set; } 27 | } 28 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Dto/Metadata.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace GarminConnectClient.Lib.Dto 5 | { 6 | /// 7 | /// Metadata 8 | /// 9 | public class Metadata 10 | { 11 | /// 12 | /// Gets or sets a value indicating whether this instance is original. 13 | /// 14 | /// 15 | /// true if this instance is original; otherwise, false. 16 | /// 17 | [JsonProperty("isOriginal")] 18 | public bool IsOriginal { get; set; } 19 | 20 | /// 21 | /// Gets or sets the device application installation identifier. 22 | /// 23 | /// 24 | /// The device application installation identifier. 25 | /// 26 | [JsonProperty("deviceApplicationInstallationId")] 27 | public long DeviceApplicationInstallationId { get; set; } 28 | 29 | /// 30 | /// Gets or sets the agent application installation identifier. 31 | /// 32 | /// 33 | /// The agent application installation identifier. 34 | /// 35 | [JsonProperty("agentApplicationInstallationId")] 36 | public object AgentApplicationInstallationId { get; set; } 37 | 38 | /// 39 | /// Gets or sets the agent string. 40 | /// 41 | /// 42 | /// The agent string. 43 | /// 44 | [JsonProperty("agentString")] 45 | public object AgentString { get; set; } 46 | 47 | /// 48 | /// Gets or sets the file format. 49 | /// 50 | /// 51 | /// The file format. 52 | /// 53 | [JsonProperty("fileFormat")] 54 | public FileFormat FileFormat { get; set; } 55 | 56 | /// 57 | /// Gets or sets the associated course identifier. 58 | /// 59 | /// 60 | /// The associated course identifier. 61 | /// 62 | [JsonProperty("associatedCourseId")] 63 | public object AssociatedCourseId { get; set; } 64 | 65 | /// 66 | /// Gets or sets the last update date. 67 | /// 68 | /// 69 | /// The last update date. 70 | /// 71 | [JsonProperty("lastUpdateDate")] 72 | public DateTimeOffset LastUpdateDate { get; set; } 73 | 74 | /// 75 | /// Gets or sets the uploaded date. 76 | /// 77 | /// 78 | /// The uploaded date. 79 | /// 80 | [JsonProperty("uploadedDate")] 81 | public DateTimeOffset UploadedDate { get; set; } 82 | 83 | /// 84 | /// Gets or sets the video URL. 85 | /// 86 | /// 87 | /// The video URL. 88 | /// 89 | [JsonProperty("videoUrl")] 90 | public object VideoUrl { get; set; } 91 | 92 | /// 93 | /// Gets or sets the has polyline. 94 | /// 95 | /// 96 | /// The has polyline. 97 | /// 98 | [JsonProperty("hasPolyline")] 99 | public bool? HasPolyline { get; set; } 100 | 101 | /// 102 | /// Gets or sets the has chart data. 103 | /// 104 | /// 105 | /// The has chart data. 106 | /// 107 | [JsonProperty("hasChartData")] 108 | public bool? HasChartData { get; set; } 109 | 110 | /// 111 | /// Gets or sets the has hr time in zones. 112 | /// 113 | /// 114 | /// The has hr time in zones. 115 | /// 116 | [JsonProperty("hasHrTimeInZones")] 117 | public bool? HasHrTimeInZones { get; set; } 118 | 119 | /// 120 | /// Gets or sets the has power time in zones. 121 | /// 122 | /// 123 | /// The has power time in zones. 124 | /// 125 | [JsonProperty("hasPowerTimeInZones")] 126 | public bool? HasPowerTimeInZones { get; set; } 127 | 128 | /// 129 | /// Gets or sets the user information dto. 130 | /// 131 | /// 132 | /// The user information dto. 133 | /// 134 | [JsonProperty("userInfoDto")] 135 | public UserInfo UserInfoDto { get; set; } 136 | 137 | /// 138 | /// Gets or sets the chart availability. 139 | /// 140 | /// 141 | /// The chart availability. 142 | /// 143 | [JsonProperty("chartAvailability")] 144 | public ChartAvailability ChartAvailability { get; set; } 145 | 146 | /// 147 | /// Gets or sets the child ids. 148 | /// 149 | /// 150 | /// The child ids. 151 | /// 152 | [JsonProperty("childIds")] 153 | public object[] ChildIds { get; set; } 154 | 155 | /// 156 | /// Gets or sets the sensors. 157 | /// 158 | /// 159 | /// The sensors. 160 | /// 161 | [JsonProperty("sensors")] 162 | public Sensor[] Sensors { get; set; } 163 | 164 | /// 165 | /// Gets or sets the activity images. 166 | /// 167 | /// 168 | /// The activity images. 169 | /// 170 | [JsonProperty("activityImages")] 171 | public ActivityImage[] ActivityImages { get; set; } 172 | 173 | /// 174 | /// Gets or sets the manufacturer. 175 | /// 176 | /// 177 | /// The manufacturer. 178 | /// 179 | [JsonProperty("manufacturer")] 180 | public string Manufacturer { get; set; } 181 | 182 | /// 183 | /// Gets or sets the dive number. 184 | /// 185 | /// 186 | /// The dive number. 187 | /// 188 | [JsonProperty("diveNumber")] 189 | public object DiveNumber { get; set; } 190 | 191 | /// 192 | /// Gets or sets the lap count. 193 | /// 194 | /// 195 | /// The lap count. 196 | /// 197 | [JsonProperty("lapCount")] 198 | public long LapCount { get; set; } 199 | 200 | /// 201 | /// Gets or sets the associated workout identifier. 202 | /// 203 | /// 204 | /// The associated workout identifier. 205 | /// 206 | [JsonProperty("associatedWorkoutId")] 207 | public object AssociatedWorkoutId { get; set; } 208 | 209 | /// 210 | /// Gets or sets the is atp activity. 211 | /// 212 | /// 213 | /// The is atp activity. 214 | /// 215 | [JsonProperty("isAtpActivity")] 216 | public object IsAtpActivity { get; set; } 217 | 218 | /// 219 | /// Gets or sets the device meta data. 220 | /// 221 | /// 222 | /// The device meta data. 223 | /// 224 | [JsonProperty("deviceMetaDataDTO")] 225 | public DeviceMetaData DeviceMetaData { get; set; } 226 | 227 | /// 228 | /// Gets or sets a value indicating whether this is GCJ02. 229 | /// 230 | /// 231 | /// true if GCJ02; otherwise, false. 232 | /// 233 | [JsonProperty("gcj02")] 234 | public bool Gcj02 { get; set; } 235 | 236 | /// 237 | /// Gets or sets a value indicating whether [automatic calculate calories]. 238 | /// 239 | /// 240 | /// true if [automatic calculate calories]; otherwise, false. 241 | /// 242 | [JsonProperty("autoCalcCalories")] 243 | public bool AutoCalcCalories { get; set; } 244 | 245 | /// 246 | /// Gets or sets a value indicating whether this is favorite. 247 | /// 248 | /// 249 | /// true if favorite; otherwise, false. 250 | /// 251 | [JsonProperty("favorite")] 252 | public bool Favorite { get; set; } 253 | 254 | /// 255 | /// Gets or sets a value indicating whether [elevation corrected]. 256 | /// 257 | /// 258 | /// true if [elevation corrected]; otherwise, false. 259 | /// 260 | [JsonProperty("elevationCorrected")] 261 | public bool ElevationCorrected { get; set; } 262 | 263 | /// 264 | /// Gets or sets a value indicating whether [personal record]. 265 | /// 266 | /// 267 | /// true if [personal record]; otherwise, false. 268 | /// 269 | [JsonProperty("personalRecord")] 270 | public bool PersonalRecord { get; set; } 271 | 272 | /// 273 | /// Gets or sets a value indicating whether [manual activity]. 274 | /// 275 | /// 276 | /// true if [manual activity]; otherwise, false. 277 | /// 278 | [JsonProperty("manualActivity")] 279 | public bool ManualActivity { get; set; } 280 | } 281 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Dto/Sensor.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace GarminConnectClient.Lib.Dto 4 | { 5 | /// 6 | /// Sensor 7 | /// 8 | public class Sensor 9 | { 10 | /// 11 | /// Gets or sets the sku. 12 | /// 13 | /// 14 | /// The sku. 15 | /// 16 | [JsonProperty("sku", NullValueHandling = NullValueHandling.Ignore)] 17 | public string Sku { get; set; } 18 | 19 | /// 20 | /// Gets or sets the type of the source. 21 | /// 22 | /// 23 | /// The type of the source. 24 | /// 25 | [JsonProperty("sourceType")] 26 | public string SourceType { get; set; } 27 | 28 | /// 29 | /// Gets or sets the software version. 30 | /// 31 | /// 32 | /// The software version. 33 | /// 34 | [JsonProperty("softwareVersion")] 35 | public double SoftwareVersion { get; set; } 36 | 37 | /// 38 | /// Gets or sets the type of the local device. 39 | /// 40 | /// 41 | /// The type of the local device. 42 | /// 43 | [JsonProperty("localDeviceType", NullValueHandling = NullValueHandling.Ignore)] 44 | public string LocalDeviceType { get; set; } 45 | } 46 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Dto/Summary.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace GarminConnectClient.Lib.Dto 5 | { 6 | /// 7 | /// Summary 8 | /// 9 | public class Summary 10 | { 11 | /// 12 | /// Gets or sets the start time local. 13 | /// 14 | /// 15 | /// The start time local. 16 | /// 17 | [JsonProperty("startTimeLocal")] 18 | public DateTimeOffset StartTimeLocal { get; set; } 19 | 20 | /// 21 | /// Gets or sets the start time GMT. 22 | /// 23 | /// 24 | /// The start time GMT. 25 | /// 26 | [JsonProperty("startTimeGMT")] 27 | public DateTimeOffset StartTimeGmt { get; set; } 28 | 29 | /// 30 | /// Gets or sets the start latitude. 31 | /// 32 | /// 33 | /// The start latitude. 34 | /// 35 | [JsonProperty("startLatitude")] 36 | public double StartLatitude { get; set; } 37 | 38 | /// 39 | /// Gets or sets the start longitude. 40 | /// 41 | /// 42 | /// The start longitude. 43 | /// 44 | [JsonProperty("startLongitude")] 45 | public double StartLongitude { get; set; } 46 | 47 | /// 48 | /// Gets or sets the distance. 49 | /// 50 | /// 51 | /// The distance. 52 | /// 53 | [JsonProperty("distance")] 54 | public long Distance { get; set; } 55 | 56 | /// 57 | /// Gets or sets the duration. 58 | /// 59 | /// 60 | /// The duration. 61 | /// 62 | [JsonProperty("duration")] 63 | public double Duration { get; set; } 64 | 65 | /// 66 | /// Gets or sets the duration of the moving. 67 | /// 68 | /// 69 | /// The duration of the moving. 70 | /// 71 | [JsonProperty("movingDuration")] 72 | public long MovingDuration { get; set; } 73 | 74 | /// 75 | /// Gets or sets the duration of the elapsed. 76 | /// 77 | /// 78 | /// The duration of the elapsed. 79 | /// 80 | [JsonProperty("elapsedDuration")] 81 | public double ElapsedDuration { get; set; } 82 | 83 | /// 84 | /// Gets or sets the elevation gain. 85 | /// 86 | /// 87 | /// The elevation gain. 88 | /// 89 | [JsonProperty("elevationGain")] 90 | public long ElevationGain { get; set; } 91 | 92 | /// 93 | /// Gets or sets the elevation loss. 94 | /// 95 | /// 96 | /// The elevation loss. 97 | /// 98 | [JsonProperty("elevationLoss")] 99 | public long ElevationLoss { get; set; } 100 | 101 | /// 102 | /// Gets or sets the maximum elevation. 103 | /// 104 | /// 105 | /// The maximum elevation. 106 | /// 107 | [JsonProperty("maxElevation")] 108 | public double MaxElevation { get; set; } 109 | 110 | /// 111 | /// Gets or sets the minimum elevation. 112 | /// 113 | /// 114 | /// The minimum elevation. 115 | /// 116 | [JsonProperty("minElevation")] 117 | public double MinElevation { get; set; } 118 | 119 | /// 120 | /// Gets or sets the average speed. 121 | /// 122 | /// 123 | /// The average speed. 124 | /// 125 | [JsonProperty("averageSpeed")] 126 | public double AverageSpeed { get; set; } 127 | 128 | /// 129 | /// Gets or sets the average moving speed. 130 | /// 131 | /// 132 | /// The average moving speed. 133 | /// 134 | [JsonProperty("averageMovingSpeed")] 135 | public double AverageMovingSpeed { get; set; } 136 | 137 | /// 138 | /// Gets or sets the maximum speed. 139 | /// 140 | /// 141 | /// The maximum speed. 142 | /// 143 | [JsonProperty("maxSpeed")] 144 | public double MaxSpeed { get; set; } 145 | 146 | /// 147 | /// Gets or sets the calories. 148 | /// 149 | /// 150 | /// The calories. 151 | /// 152 | [JsonProperty("calories")] 153 | public double Calories { get; set; } 154 | 155 | /// 156 | /// Gets or sets the average hr. 157 | /// 158 | /// 159 | /// The average hr. 160 | /// 161 | [JsonProperty("averageHR")] 162 | public long AverageHr { get; set; } 163 | 164 | /// 165 | /// Gets or sets the maximum hr. 166 | /// 167 | /// 168 | /// The maximum hr. 169 | /// 170 | [JsonProperty("maxHR")] 171 | public long MaxHr { get; set; } 172 | 173 | /// 174 | /// Gets or sets the average run cadence. 175 | /// 176 | /// 177 | /// The average run cadence. 178 | /// 179 | [JsonProperty("averageRunCadence")] 180 | public double AverageRunCadence { get; set; } 181 | 182 | /// 183 | /// Gets or sets the maximum run cadence. 184 | /// 185 | /// 186 | /// The maximum run cadence. 187 | /// 188 | [JsonProperty("maxRunCadence")] 189 | public long MaxRunCadence { get; set; } 190 | 191 | /// 192 | /// Gets or sets the average temperature. 193 | /// 194 | /// 195 | /// The average temperature. 196 | /// 197 | [JsonProperty("averageTemperature")] 198 | public double AverageTemperature { get; set; } 199 | 200 | /// 201 | /// Gets or sets the maximum temperature. 202 | /// 203 | /// 204 | /// The maximum temperature. 205 | /// 206 | [JsonProperty("maxTemperature")] 207 | public long MaxTemperature { get; set; } 208 | 209 | /// 210 | /// Gets or sets the minimum temperature. 211 | /// 212 | /// 213 | /// The minimum temperature. 214 | /// 215 | [JsonProperty("minTemperature")] 216 | public long MinTemperature { get; set; } 217 | 218 | /// 219 | /// Gets or sets the length of the stride. 220 | /// 221 | /// 222 | /// The length of the stride. 223 | /// 224 | [JsonProperty("strideLength")] 225 | public double StrideLength { get; set; } 226 | 227 | /// 228 | /// Gets or sets the training effect. 229 | /// 230 | /// 231 | /// The training effect. 232 | /// 233 | [JsonProperty("trainingEffect")] 234 | public double TrainingEffect { get; set; } 235 | 236 | /// 237 | /// Gets or sets the anaerobic training effect. 238 | /// 239 | /// 240 | /// The anaerobic training effect. 241 | /// 242 | [JsonProperty("anaerobicTrainingEffect")] 243 | public double AnaerobicTrainingEffect { get; set; } 244 | 245 | /// 246 | /// Gets or sets the aerobic training effect message. 247 | /// 248 | /// 249 | /// The aerobic training effect message. 250 | /// 251 | [JsonProperty("aerobicTrainingEffectMessage")] 252 | public string AerobicTrainingEffectMessage { get; set; } 253 | 254 | /// 255 | /// Gets or sets the anaerobic training effect message. 256 | /// 257 | /// 258 | /// The anaerobic training effect message. 259 | /// 260 | [JsonProperty("anaerobicTrainingEffectMessage")] 261 | public string AnaerobicTrainingEffectMessage { get; set; } 262 | 263 | /// 264 | /// Gets or sets the end latitude. 265 | /// 266 | /// 267 | /// The end latitude. 268 | /// 269 | [JsonProperty("endLatitude")] 270 | public double EndLatitude { get; set; } 271 | 272 | /// 273 | /// Gets or sets the end longitude. 274 | /// 275 | /// 276 | /// The end longitude. 277 | /// 278 | [JsonProperty("endLongitude")] 279 | public double EndLongitude { get; set; } 280 | 281 | /// 282 | /// Gets or sets a value indicating whether this instance is deco dive. 283 | /// 284 | /// 285 | /// true if this instance is deco dive; otherwise, false. 286 | /// 287 | [JsonProperty("isDecoDive")] 288 | public bool IsDecoDive { get; set; } 289 | 290 | /// 291 | /// Gets or sets the average vertical speed. 292 | /// 293 | /// 294 | /// The average vertical speed. 295 | /// 296 | [JsonProperty("avgVerticalSpeed")] 297 | public long AvgVerticalSpeed { get; set; } 298 | 299 | /// 300 | /// Gets or sets the maximum vertical speed. 301 | /// 302 | /// 303 | /// The maximum vertical speed. 304 | /// 305 | [JsonProperty("maxVerticalSpeed")] 306 | public double MaxVerticalSpeed { get; set; } 307 | } 308 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Dto/TimeZoneUnit.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace GarminConnectClient.Lib.Dto 4 | { 5 | /// 6 | /// Time zone 7 | /// 8 | public class TimeZoneUnit 9 | { 10 | /// 11 | /// Gets or sets the unit identifier. 12 | /// 13 | /// 14 | /// The unit identifier. 15 | /// 16 | [JsonProperty("unitId")] 17 | public long UnitId { get; set; } 18 | 19 | /// 20 | /// Gets or sets the unit key. 21 | /// 22 | /// 23 | /// The unit key. 24 | /// 25 | [JsonProperty("unitKey")] 26 | public string UnitKey { get; set; } 27 | 28 | /// 29 | /// Gets or sets the factor. 30 | /// 31 | /// 32 | /// The factor. 33 | /// 34 | [JsonProperty("factor")] 35 | public long Factor { get; set; } 36 | 37 | /// 38 | /// Gets or sets the time zone. 39 | /// 40 | /// 41 | /// The time zone. 42 | /// 43 | [JsonProperty("timeZone")] 44 | public string TimeZone { get; set; } 45 | } 46 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Dto/UserInfo.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace GarminConnectClient.Lib.Dto 5 | { 6 | /// 7 | /// User info 8 | /// 9 | public class UserInfo 10 | { 11 | /// 12 | /// Gets or sets the identifier. 13 | /// 14 | /// 15 | /// The identifier. 16 | /// 17 | [JsonProperty("id")] 18 | public long Id { get; set; } 19 | 20 | /// 21 | /// Gets or sets the profile identifier. 22 | /// 23 | /// 24 | /// The profile identifier. 25 | /// 26 | [JsonProperty("profileId")] 27 | public long ProfileId { get; set; } 28 | 29 | /// 30 | /// Gets or sets the garmin unique identifier. 31 | /// 32 | /// 33 | /// The garmin unique identifier. 34 | /// 35 | [JsonProperty("garminGUID")] 36 | public Guid GarminGuid { get; set; } 37 | 38 | /// 39 | /// Gets or sets the display name. 40 | /// 41 | /// 42 | /// The display name. 43 | /// 44 | [JsonProperty("displayName")] 45 | public Guid DisplayName { get; set; } 46 | 47 | /// 48 | /// Gets or sets the full name. 49 | /// 50 | /// 51 | /// The full name. 52 | /// 53 | [JsonProperty("fullName")] 54 | public string FullName { get; set; } 55 | 56 | /// 57 | /// Gets or sets the name of the user. 58 | /// 59 | /// 60 | /// The name of the user. 61 | /// 62 | [JsonProperty("userName")] 63 | public string UserName { get; set; } 64 | 65 | /// 66 | /// Gets or sets the profile image URL large. 67 | /// 68 | /// 69 | /// The profile image URL large. 70 | /// 71 | [JsonProperty("profileImageUrlLarge")] 72 | public Uri ProfileImageUrlLarge { get; set; } 73 | 74 | /// 75 | /// Gets or sets the profile image URL medium. 76 | /// 77 | /// 78 | /// The profile image URL medium. 79 | /// 80 | [JsonProperty("profileImageUrlMedium")] 81 | public Uri ProfileImageUrlMedium { get; set; } 82 | 83 | /// 84 | /// Gets or sets the profile image URL small. 85 | /// 86 | /// 87 | /// The profile image URL small. 88 | /// 89 | [JsonProperty("profileImageUrlSmall")] 90 | public Uri ProfileImageUrlSmall { get; set; } 91 | 92 | /// 93 | /// Gets or sets the location. 94 | /// 95 | /// 96 | /// The location. 97 | /// 98 | [JsonProperty("location")] 99 | public object Location { get; set; } 100 | 101 | /// 102 | /// Gets or sets the facebook URL. 103 | /// 104 | /// 105 | /// The facebook URL. 106 | /// 107 | [JsonProperty("facebookUrl")] 108 | public object FacebookUrl { get; set; } 109 | 110 | /// 111 | /// Gets or sets the twitter URL. 112 | /// 113 | /// 114 | /// The twitter URL. 115 | /// 116 | [JsonProperty("twitterUrl")] 117 | public object TwitterUrl { get; set; } 118 | 119 | /// 120 | /// Gets or sets the personal website. 121 | /// 122 | /// 123 | /// The personal website. 124 | /// 125 | [JsonProperty("personalWebsite")] 126 | public object PersonalWebsite { get; set; } 127 | 128 | /// 129 | /// Gets or sets the motivation. 130 | /// 131 | /// 132 | /// The motivation. 133 | /// 134 | [JsonProperty("motivation")] 135 | public long Motivation { get; set; } 136 | 137 | /// 138 | /// Gets or sets the bio. 139 | /// 140 | /// 141 | /// The bio. 142 | /// 143 | [JsonProperty("bio")] 144 | public object Bio { get; set; } 145 | 146 | /// 147 | /// Gets or sets the primary activity. 148 | /// 149 | /// 150 | /// The primary activity. 151 | /// 152 | [JsonProperty("primaryActivity")] 153 | public string PrimaryActivity { get; set; } 154 | 155 | /// 156 | /// Gets or sets the favorite activity types. 157 | /// 158 | /// 159 | /// The favorite activity types. 160 | /// 161 | [JsonProperty("favoriteActivityTypes")] 162 | public string[] FavoriteActivityTypes { get; set; } 163 | 164 | /// 165 | /// Gets or sets the running training speed. 166 | /// 167 | /// 168 | /// The running training speed. 169 | /// 170 | [JsonProperty("runningTrainingSpeed")] 171 | public double RunningTrainingSpeed { get; set; } 172 | 173 | /// 174 | /// Gets or sets the cycling training speed. 175 | /// 176 | /// 177 | /// The cycling training speed. 178 | /// 179 | [JsonProperty("cyclingTrainingSpeed")] 180 | public long CyclingTrainingSpeed { get; set; } 181 | 182 | /// 183 | /// Gets or sets the favorite cycling activity types. 184 | /// 185 | /// 186 | /// The favorite cycling activity types. 187 | /// 188 | [JsonProperty("favoriteCyclingActivityTypes")] 189 | public string[] FavoriteCyclingActivityTypes { get; set; } 190 | 191 | /// 192 | /// Gets or sets the cycling classification. 193 | /// 194 | /// 195 | /// The cycling classification. 196 | /// 197 | [JsonProperty("cyclingClassification")] 198 | public string CyclingClassification { get; set; } 199 | 200 | /// 201 | /// Gets or sets the cycling maximum average power. 202 | /// 203 | /// 204 | /// The cycling maximum average power. 205 | /// 206 | [JsonProperty("cyclingMaxAvgPower")] 207 | public long CyclingMaxAvgPower { get; set; } 208 | 209 | /// 210 | /// Gets or sets the swimming training speed. 211 | /// 212 | /// 213 | /// The swimming training speed. 214 | /// 215 | [JsonProperty("swimmingTrainingSpeed")] 216 | public double SwimmingTrainingSpeed { get; set; } 217 | 218 | /// 219 | /// Gets or sets the profile visibility. 220 | /// 221 | /// 222 | /// The profile visibility. 223 | /// 224 | [JsonProperty("profileVisibility")] 225 | public string ProfileVisibility { get; set; } 226 | 227 | /// 228 | /// Gets or sets the activity start visibility. 229 | /// 230 | /// 231 | /// The activity start visibility. 232 | /// 233 | [JsonProperty("activityStartVisibility")] 234 | public string ActivityStartVisibility { get; set; } 235 | 236 | /// 237 | /// Gets or sets the activity map visibility. 238 | /// 239 | /// 240 | /// The activity map visibility. 241 | /// 242 | [JsonProperty("activityMapVisibility")] 243 | public string ActivityMapVisibility { get; set; } 244 | 245 | /// 246 | /// Gets or sets the course visibility. 247 | /// 248 | /// 249 | /// The course visibility. 250 | /// 251 | [JsonProperty("courseVisibility")] 252 | public string CourseVisibility { get; set; } 253 | 254 | /// 255 | /// Gets or sets the activity heart rate visibility. 256 | /// 257 | /// 258 | /// The activity heart rate visibility. 259 | /// 260 | [JsonProperty("activityHeartRateVisibility")] 261 | public string ActivityHeartRateVisibility { get; set; } 262 | 263 | /// 264 | /// Gets or sets the activity power visibility. 265 | /// 266 | /// 267 | /// The activity power visibility. 268 | /// 269 | [JsonProperty("activityPowerVisibility")] 270 | public string ActivityPowerVisibility { get; set; } 271 | 272 | /// 273 | /// Gets or sets the badge visibility. 274 | /// 275 | /// 276 | /// The badge visibility. 277 | /// 278 | [JsonProperty("badgeVisibility")] 279 | public string BadgeVisibility { get; set; } 280 | 281 | /// 282 | /// Gets or sets a value indicating whether [show age]. 283 | /// 284 | /// 285 | /// true if [show age]; otherwise, false. 286 | /// 287 | [JsonProperty("showAge")] 288 | public bool ShowAge { get; set; } 289 | 290 | /// 291 | /// Gets or sets a value indicating whether [show weight]. 292 | /// 293 | /// 294 | /// true if [show weight]; otherwise, false. 295 | /// 296 | [JsonProperty("showWeight")] 297 | public bool ShowWeight { get; set; } 298 | 299 | /// 300 | /// Gets or sets a value indicating whether [show height]. 301 | /// 302 | /// 303 | /// true if [show height]; otherwise, false. 304 | /// 305 | [JsonProperty("showHeight")] 306 | public bool ShowHeight { get; set; } 307 | 308 | /// 309 | /// Gets or sets a value indicating whether [show weight class]. 310 | /// 311 | /// 312 | /// true if [show weight class]; otherwise, false. 313 | /// 314 | [JsonProperty("showWeightClass")] 315 | public bool ShowWeightClass { get; set; } 316 | 317 | /// 318 | /// Gets or sets a value indicating whether [show age range]. 319 | /// 320 | /// 321 | /// true if [show age range]; otherwise, false. 322 | /// 323 | [JsonProperty("showAgeRange")] 324 | public bool ShowAgeRange { get; set; } 325 | 326 | /// 327 | /// Gets or sets a value indicating whether [show gender]. 328 | /// 329 | /// 330 | /// true if [show gender]; otherwise, false. 331 | /// 332 | [JsonProperty("showGender")] 333 | public bool ShowGender { get; set; } 334 | 335 | /// 336 | /// Gets or sets a value indicating whether [show activity class]. 337 | /// 338 | /// 339 | /// true if [show activity class]; otherwise, false. 340 | /// 341 | [JsonProperty("showActivityClass")] 342 | public bool ShowActivityClass { get; set; } 343 | 344 | /// 345 | /// Gets or sets a value indicating whether [show vo2 maximum]. 346 | /// 347 | /// 348 | /// true if [show vo2 maximum]; otherwise, false. 349 | /// 350 | [JsonProperty("showVO2Max")] 351 | public bool ShowVo2Max { get; set; } 352 | 353 | /// 354 | /// Gets or sets a value indicating whether [show personal records]. 355 | /// 356 | /// 357 | /// true if [show personal records]; otherwise, false. 358 | /// 359 | [JsonProperty("showPersonalRecords")] 360 | public bool ShowPersonalRecords { get; set; } 361 | 362 | /// 363 | /// Gets or sets a value indicating whether [show last12 months]. 364 | /// 365 | /// 366 | /// true if [show last12 months]; otherwise, false. 367 | /// 368 | [JsonProperty("showLast12Months")] 369 | public bool ShowLast12Months { get; set; } 370 | 371 | /// 372 | /// Gets or sets a value indicating whether [show lifetime totals]. 373 | /// 374 | /// 375 | /// true if [show lifetime totals]; otherwise, false. 376 | /// 377 | [JsonProperty("showLifetimeTotals")] 378 | public bool ShowLifetimeTotals { get; set; } 379 | 380 | /// 381 | /// Gets or sets a value indicating whether [show upcoming events]. 382 | /// 383 | /// 384 | /// true if [show upcoming events]; otherwise, false. 385 | /// 386 | [JsonProperty("showUpcomingEvents")] 387 | public bool ShowUpcomingEvents { get; set; } 388 | 389 | /// 390 | /// Gets or sets a value indicating whether [show recent favorites]. 391 | /// 392 | /// 393 | /// true if [show recent favorites]; otherwise, false. 394 | /// 395 | [JsonProperty("showRecentFavorites")] 396 | public bool ShowRecentFavorites { get; set; } 397 | 398 | /// 399 | /// Gets or sets a value indicating whether [show recent device]. 400 | /// 401 | /// 402 | /// true if [show recent device]; otherwise, false. 403 | /// 404 | [JsonProperty("showRecentDevice")] 405 | public bool ShowRecentDevice { get; set; } 406 | 407 | /// 408 | /// Gets or sets a value indicating whether [show recent gear]. 409 | /// 410 | /// 411 | /// true if [show recent gear]; otherwise, false. 412 | /// 413 | [JsonProperty("showRecentGear")] 414 | public bool ShowRecentGear { get; set; } 415 | 416 | /// 417 | /// Gets or sets a value indicating whether [show badges]. 418 | /// 419 | /// 420 | /// true if [show badges]; otherwise, false. 421 | /// 422 | [JsonProperty("showBadges")] 423 | public bool ShowBadges { get; set; } 424 | 425 | /// 426 | /// Gets or sets the other activity. 427 | /// 428 | /// 429 | /// The other activity. 430 | /// 431 | [JsonProperty("otherActivity")] 432 | public object OtherActivity { get; set; } 433 | 434 | /// 435 | /// Gets or sets the other primary activity. 436 | /// 437 | /// 438 | /// The other primary activity. 439 | /// 440 | [JsonProperty("otherPrimaryActivity")] 441 | public object OtherPrimaryActivity { get; set; } 442 | 443 | /// 444 | /// Gets or sets the other motivation. 445 | /// 446 | /// 447 | /// The other motivation. 448 | /// 449 | [JsonProperty("otherMotivation")] 450 | public object OtherMotivation { get; set; } 451 | 452 | /// 453 | /// Gets or sets the user roles. 454 | /// 455 | /// 456 | /// The user roles. 457 | /// 458 | [JsonProperty("userRoles")] 459 | public string[] UserRoles { get; set; } 460 | 461 | /// 462 | /// Gets or sets a value indicating whether [name approved]. 463 | /// 464 | /// 465 | /// true if [name approved]; otherwise, false. 466 | /// 467 | [JsonProperty("nameApproved")] 468 | public bool NameApproved { get; set; } 469 | 470 | /// 471 | /// Gets or sets the full name of the user profile. 472 | /// 473 | /// 474 | /// The full name of the user profile. 475 | /// 476 | [JsonProperty("userProfileFullName")] 477 | public string UserProfileFullName { get; set; } 478 | 479 | /// 480 | /// Gets or sets a value indicating whether [make golf scorecards private]. 481 | /// 482 | /// 483 | /// true if [make golf scorecards private]; otherwise, false. 484 | /// 485 | [JsonProperty("makeGolfScorecardsPrivate")] 486 | public bool MakeGolfScorecardsPrivate { get; set; } 487 | 488 | /// 489 | /// Gets or sets a value indicating whether [allow golf live scoring]. 490 | /// 491 | /// 492 | /// true if [allow golf live scoring]; otherwise, false. 493 | /// 494 | [JsonProperty("allowGolfLiveScoring")] 495 | public bool AllowGolfLiveScoring { get; set; } 496 | 497 | /// 498 | /// Gets or sets a value indicating whether [allow golf scoring by connections]. 499 | /// 500 | /// 501 | /// true if [allow golf scoring by connections]; otherwise, false. 502 | /// 503 | [JsonProperty("allowGolfScoringByConnections")] 504 | public bool AllowGolfScoringByConnections { get; set; } 505 | 506 | /// 507 | /// Gets or sets the user level. 508 | /// 509 | /// 510 | /// The user level. 511 | /// 512 | [JsonProperty("userLevel")] 513 | public long UserLevel { get; set; } 514 | 515 | /// 516 | /// Gets or sets the user point. 517 | /// 518 | /// 519 | /// The user point. 520 | /// 521 | [JsonProperty("userPoint")] 522 | public long UserPoint { get; set; } 523 | 524 | /// 525 | /// Gets or sets the level update date. 526 | /// 527 | /// 528 | /// The level update date. 529 | /// 530 | [JsonProperty("levelUpdateDate")] 531 | public DateTimeOffset LevelUpdateDate { get; set; } 532 | 533 | /// 534 | /// Gets or sets a value indicating whether [level is viewed]. 535 | /// 536 | /// 537 | /// true if [level is viewed]; otherwise, false. 538 | /// 539 | [JsonProperty("levelIsViewed")] 540 | public bool LevelIsViewed { get; set; } 541 | 542 | /// 543 | /// Gets or sets the level point threshold. 544 | /// 545 | /// 546 | /// The level point threshold. 547 | /// 548 | [JsonProperty("levelPointThreshold")] 549 | public long LevelPointThreshold { get; set; } 550 | 551 | /// 552 | /// Gets or sets a value indicating whether [user pro]. 553 | /// 554 | /// 555 | /// true if [user pro]; otherwise, false. 556 | /// 557 | [JsonProperty("userPro")] 558 | public bool UserPro { get; set; } 559 | } 560 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Enum/ActivityFileTypeEnum.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 1591 2 | 3 | namespace GarminConnectClient.Lib.Enum 4 | { 5 | /// 6 | /// Garmin Connect export format enum 7 | /// 8 | public enum ActivityFileTypeEnum 9 | { 10 | Gpx, 11 | Csv, 12 | Fit, 13 | Kml, 14 | Tcx 15 | } 16 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Enum/ActivityTypeEnum.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 1591 2 | 3 | namespace GarminConnectClient.Lib.Enum 4 | { 5 | /// 6 | /// Garmin Connect export format enum 7 | /// 8 | public enum ActivityTypeEnum 9 | { 10 | Running = 1, 11 | Cycling = 2, 12 | Hiking = 3, 13 | Other = 4, 14 | MountainBiking = 5, 15 | TrailRunning = 6, 16 | StreetRunning = 7, 17 | TrackRunning = 8, 18 | Walking = 9, 19 | RoadBiking = 10, 20 | IndoorCardio = 11, 21 | Uncategorized = 12, 22 | StrengthTraining = 13, 23 | CasualWalking = 15, 24 | SpeedWalking = 16, 25 | All = 17, 26 | TreadmillRunning = 18, 27 | CycloCross = 19, 28 | DownhillBiking = 20, 29 | TrackCycling = 21, 30 | RecumbentCycling = 22, 31 | IndoorCycling = 25, 32 | Swimming = 26, 33 | LapSwimming = 27, 34 | OpenWaterSwimming = 28, 35 | FitnessEquipment = 29, 36 | Elliptical = 30, 37 | StairClimbing = 31, 38 | IndoorRowing = 32, 39 | SnowShoe = 36, 40 | Mountaineering = 37, 41 | Rowing = 39, 42 | WindKiteSurfing = 41, 43 | HorsebackRiding = 44, 44 | DrivingGeneral = 49, 45 | Snowmobiling = 51, 46 | Flying = 52, 47 | Paddling = 57, 48 | WhitewaterRaftingKayaking = 60, 49 | Skating = 62, 50 | InlineSkating = 63, 51 | ResortSkiingSnowboarding = 67, 52 | BackCountrySkiingSnowboarding = 68, 53 | Motorcycling = 71, 54 | Boating = 75, 55 | Sailing = 77, 56 | CrossCountrySkiing = 81, 57 | SkateSkiing = 82, 58 | Transition = 83, 59 | SwimToBikeTransition = 84, 60 | BikeToRunTransition = 85, 61 | RunToBikeTransition = 86, 62 | StandUpPaddleBoarding = 87, 63 | Golf = 88, 64 | MultiSport = 89, 65 | Steps = 108, 66 | Bmx = 131, 67 | Atv = 132, 68 | HuntingFishing = 133, 69 | SkyDiving = 134, 70 | Motocross = 135, 71 | RcDrone = 136, 72 | Surfing = 137, 73 | Wakeboarding = 138, 74 | RockClimbing = 139, 75 | HangGliding = 140, 76 | WingsuitFlying = 141, 77 | Tennis = 142, 78 | GravelCycling = 143, 79 | Diving = 144, 80 | SingleGasDiving = 145, 81 | MultiGasDiving = 146, 82 | GaugeDiving = 147, 83 | ApneaDiving = 148, 84 | Yoga = 149, 85 | FloorClimbing = 150, 86 | StopWatch = 151, 87 | VirtualRide = 152, 88 | VirtualRun = 153, 89 | ObstacleRun = 154, 90 | ApneaHunting = 155, 91 | IndoorRunning = 156, 92 | CcrDiving = 161 93 | } 94 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Enum/EventTypeEnum.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 1591 2 | 3 | namespace GarminConnectClient.Lib.Enum 4 | { 5 | /// 6 | /// Garmin Connect export format enum 7 | /// 8 | public enum EventTypeEnum 9 | { 10 | All = 10, 11 | SpecialEvent = 3, 12 | Race = 1, 13 | Fitness = 8, 14 | Recreation = 2, 15 | Touring = 6, 16 | Training = 4, 17 | Uncategorized = 9, 18 | Transportation = 5, 19 | Geocaching = 7 20 | } 21 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/GarminConnectClient.Lib.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | GarminConnectClient is a library for communication with GarminConnect. It allows to list, download and upload Garmin activities. It is inspired by https://github.com/La0/garmin-uploader. 6 | © 2020 Marek Polak 7 | https://github.com/marazt/garmin-connect-client/blob/master/LICENSE.md 8 | https://github.com/marazt/garmin-connect-client 9 | https://github.com/marazt/garmin-connect-client 10 | git 11 | garmin garminconnect backup activity 12 | Fix of the new GarminConnect login workflow. 13 | GarminConnectClientNet 14 | Marazt 15 | Marazt 16 | true 17 | 1.2.0 18 | 19 | 20 | 21 | F:\Projects\GarminConnectClient\GarminConnectClient.Lib\GarminConnectClient.Lib.xml 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /GarminConnectClient.Lib/IConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace GarminConnectClient.Lib 2 | { 3 | /// 4 | /// Configuration interface 5 | /// 6 | public interface IConfiguration 7 | { 8 | /// 9 | /// Gets the username. 10 | /// 11 | /// 12 | /// The username. 13 | /// 14 | string Username { get; } 15 | 16 | /// 17 | /// Gets the password. 18 | /// 19 | /// 20 | /// The password. 21 | /// 22 | string Password { get; } 23 | 24 | /// 25 | /// Gets the backup dir. 26 | /// 27 | /// 28 | /// The backup dir. 29 | /// 30 | string BackupDir { get; } 31 | 32 | /// 33 | /// Gets the storage connection string. 34 | /// 35 | /// 36 | /// The storage connection string. 37 | /// 38 | string StorageConnectionString { get; } 39 | 40 | /// 41 | /// Gets the name of the container. 42 | /// 43 | /// 44 | /// The name of the container. 45 | /// 46 | string ContainerName { get; } 47 | } 48 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | FileSystem 8 | Release 9 | Any CPU 10 | netstandard2.0 11 | bin\Release\netstandard2.0\publish\ 12 | 13 | -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Services/Client.cs: -------------------------------------------------------------------------------- 1 | using GarminConnectClient.Lib.Dto; 2 | using GarminConnectClient.Lib.Enum; 3 | using Microsoft.Extensions.Logging; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Globalization; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Net; 12 | using System.Net.Http; 13 | using System.Net.Http.Headers; 14 | using System.Text; 15 | using System.Text.RegularExpressions; 16 | using System.Threading.Tasks; 17 | 18 | namespace GarminConnectClient.Lib.Services 19 | { 20 | /// 21 | /// 22 | /// Client implementation. 23 | /// Inspired by https://github.com/La0/garmin-uploader 24 | /// 25 | /// 26 | public class Client : IClient 27 | { 28 | private const string LOCALE = "en_US"; 29 | private const string USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0"; 30 | private const string CONNECT_DNS = "connect.garmin.com"; 31 | private const string CONNECT_URL = "https://" + CONNECT_DNS; 32 | private const string CONNECT_URL_MODERN = CONNECT_URL + "/modern/"; 33 | private const string CONNECT_URL_SIGNIN = CONNECT_URL + "/signin/"; 34 | private const string SSO_DNS = "sso.garmin.com"; 35 | private const string SSO_URL = "https://" + SSO_DNS; 36 | private const string SSO_URL_SSO = SSO_URL + "/sso"; 37 | private const string SSO_URL_SSO_SIGNIN = SSO_URL_SSO + "/signin"; 38 | private const string CONNECT_URL_PROFILE = CONNECT_URL_MODERN + "proxy/userprofile-service/socialProfile/"; 39 | private const string CONNECT_MODERN_HOSTNAME = "https://connect.garmin.com/modern/auth/hostname"; 40 | private const string CSS_URL = CONNECT_URL + "/gauth-custom-v1.2-min.css"; 41 | private const string PRIVACY_STATEMENT_URL = "https://www.garmin.com/en-US/privacy/connect/"; 42 | private const string URL_UPLOAD = CONNECT_URL + "/modern/proxy/upload-service/upload"; 43 | private const string URL_ACTIVITY_BASE = CONNECT_URL + "/modern/proxy/activity-service/activity"; 44 | 45 | private const string UrlActivityTypes = 46 | "https://connect.garmin.com/modern/proxy/activity-service/activity/activityTypes"; 47 | 48 | private const string UrlEventTypes = 49 | "https://connect.garmin.com/modern/proxy/activity-service/activity/eventTypes"; 50 | 51 | private const string UrlActivitiesBase = 52 | "https://connect.garmin.com/modern/proxy/activitylist-service/activities/search/activities"; 53 | 54 | private const string UrlActivityDownloadFile = 55 | " https://connect.garmin.com/modern/proxy/download-service/export/{0}/activity/{1}"; 56 | 57 | private const string UrlActivityDownloadDefaultFile = 58 | " https://connect.garmin.com/modern/proxy/download-service/files/activity/{0}"; 59 | 60 | private const ActivityFileTypeEnum DefaultFile = ActivityFileTypeEnum.Fit; 61 | 62 | private static CookieContainer cookieContainer; 63 | private static HttpClientHandler clientHandler; 64 | private HttpClient httpClient; 65 | 66 | private static readonly Tuple BaseHeader = new Tuple("NK", "NT"); 67 | 68 | private static readonly Dictionary QueryParams = new Dictionary 69 | { 70 | {"clientId", "GarminConnect"}, 71 | {"connectLegalTerms", "true"}, 72 | {"consumeServiceTicket", "false"}, 73 | {"createAccountShown", "true"}, 74 | {"cssUrl", CSS_URL}, 75 | {"displayNameShown", "false"}, 76 | {"embedWidget", "false"}, 77 | // ReSharper disable once StringLiteralTypo 78 | {"gauthHost", SSO_URL_SSO}, 79 | {"generateExtraServiceTicket", "true"}, 80 | {"generateTwoExtraServiceTickets", "false"}, 81 | {"generateNoServiceTicket", "false"}, 82 | {"globalOptInChecked", "false"}, 83 | {"globalOptInShown", "true"}, 84 | // ReSharper disable once StringLiteralTypo 85 | {"id", "gauth-widget"}, 86 | {"initialFocus", "true"}, 87 | {"locale", LOCALE}, 88 | {"locationPromptShon", "true"}, 89 | {"mobile", "false"}, 90 | {"openCreateAccount", "false"}, 91 | {"privacyStatementUrl", PRIVACY_STATEMENT_URL}, 92 | {"redirectAfterAccountCreationUrl", CONNECT_URL_MODERN}, 93 | {"redirectAfterAccountLoginUrl", CONNECT_URL_MODERN}, 94 | {"rememberMeChecked", "false"}, 95 | {"rememberMeShown", "true"}, 96 | {"service", CONNECT_URL_MODERN}, 97 | {"showTermsOfUse", "false"}, 98 | {"showPrivacyPolicy", "false"}, 99 | {"showConnectLegalAge", "false"}, 100 | {"showPassword", "true"}, 101 | {"source", CONNECT_URL_SIGNIN}, 102 | // {"usernameShown", "false"}, 103 | {"useCustomHeader", "false"}, 104 | {"webhost", CONNECT_URL_MODERN} 105 | }; 106 | 107 | /// 108 | /// The configuration 109 | /// 110 | private readonly IConfiguration configuration; 111 | 112 | /// 113 | /// The logger 114 | /// 115 | // ReSharper disable once NotAccessedField.Local 116 | private readonly ILogger logger; 117 | 118 | /// 119 | /// Initializes a new instance of the class. 120 | /// 121 | /// The configuration. 122 | /// The logger. 123 | public Client(IConfiguration configuration, ILogger logger) 124 | { 125 | this.configuration = configuration; 126 | this.logger = logger; 127 | } 128 | 129 | /// 130 | /// 131 | /// Authenticates this instance. 132 | /// 133 | /// 134 | /// Tuple of Cookies and HTTP handler 135 | /// 136 | /// 137 | /// SSO hostname is missing 138 | /// or 139 | /// Could not match service ticket. 140 | /// 141 | public async Task<(CookieContainer, HttpClientHandler)> Authenticate() 142 | { 143 | cookieContainer = new CookieContainer(); 144 | clientHandler = 145 | new HttpClientHandler 146 | { 147 | AllowAutoRedirect = true, 148 | UseCookies = true, 149 | CookieContainer = cookieContainer 150 | }; 151 | 152 | this.httpClient = new HttpClient(clientHandler); 153 | 154 | this.httpClient.DefaultRequestHeaders.Add("user-agent", USER_AGENT); 155 | var data = await this.httpClient.GetStringAsync(CONNECT_MODERN_HOSTNAME); 156 | 157 | var ssoHostname = JObject.Parse(data)["host"] == null 158 | ? throw new Exception("SSO hostname is missing") 159 | : JObject.Parse(data)["host"].ToString(); 160 | 161 | var queryParams = string.Join("&", QueryParams.Select(e => $"{e.Key}={WebUtility.UrlEncode(e.Value)}")); 162 | 163 | var url = $"{SSO_URL_SSO_SIGNIN}?{queryParams}"; 164 | var res = await this.httpClient.GetAsync(url); 165 | ValidateResponseMessage(res, "No login form."); 166 | 167 | data = await res.Content.ReadAsStringAsync(); 168 | var csrfToken = ""; 169 | try 170 | { 171 | GetValueByPattern(data, @"input type=\""hidden\"" name=\""_csrf\"" value=\""(\w+)\"" \/>", 2, 1); 172 | } 173 | catch (Exception e) 174 | { 175 | this.logger.LogError("Exception finding token by pattern: ", e); 176 | this.logger.LogError($"data:\n{data}"); 177 | throw e; 178 | } 179 | 180 | this.httpClient.DefaultRequestHeaders.Add("origin", SSO_URL); 181 | this.httpClient.DefaultRequestHeaders.Add("referer", url); 182 | 183 | var formContent = new FormUrlEncodedContent(new[] 184 | { 185 | new KeyValuePair("embed", "false"), 186 | new KeyValuePair("username", this.configuration.Username), 187 | new KeyValuePair("password", this.configuration.Password), 188 | new KeyValuePair("_csrf", csrfToken) 189 | }); 190 | 191 | res = await this.httpClient.PostAsync(url, formContent); 192 | data = await res.Content.ReadAsStringAsync(); 193 | ValidateResponseMessage(res, $"Bad response {res.StatusCode}, expected {HttpStatusCode.OK}"); 194 | ValidateCookiePresence(cookieContainer, "GARMIN-SSO-GUID"); 195 | 196 | var ticket = GetValueByPattern(data, @"var response_url(\s+)= (\""|\').*?ticket=([\w\-]+)(\""|\')", 5, 3); 197 | 198 | // Second auth step 199 | // Needs a service ticket from previous response 200 | this.httpClient.DefaultRequestHeaders.Remove("origin"); 201 | url = $"{CONNECT_URL_MODERN}?ticket={WebUtility.UrlEncode(ticket)}"; 202 | res = await this.httpClient.GetAsync(url); 203 | 204 | ValidateModernTicketUrlResponseMessage(res, $"Second auth step failed to produce success or expected 302: {res.StatusCode}."); 205 | 206 | // Check session cookie 207 | ValidateCookiePresence(cookieContainer, "SESSIONID"); 208 | 209 | // Check login 210 | res = await this.httpClient.GetAsync(CONNECT_URL_PROFILE); 211 | ValidateResponseMessage(res, "Login check failed."); 212 | 213 | return (cookieContainer, clientHandler); 214 | } 215 | 216 | /// 217 | /// Gets the value by pattern. 218 | /// 219 | /// The data. 220 | /// The pattern. 221 | /// The expected count of groups. 222 | /// The group position. 223 | /// Value of particular match group. 224 | /// Could not match expected pattern {pattern} 225 | private static string GetValueByPattern(string data, string pattern, int expectedCountOfGroups, int groupPosition) 226 | { 227 | var regex = new Regex(pattern); 228 | var match = regex.Match(data); 229 | if (!match.Success || match.Groups.Count != expectedCountOfGroups) 230 | { 231 | throw new Exception($"Could not match expected pattern {pattern}."); 232 | } 233 | return match.Groups[groupPosition].Value; 234 | } 235 | 236 | /// 237 | /// Validates the cookie presence. 238 | /// 239 | /// The container. 240 | /// Name of the cookie. 241 | /// Missing cookie {cookieName} 242 | private static void ValidateCookiePresence(CookieContainer container, string cookieName) 243 | { 244 | var cookies = container.GetCookies(new Uri(CONNECT_URL_MODERN)).Cast().ToList(); 245 | if (!cookies.Any(e => string.Equals(cookieName, e.Name, StringComparison.InvariantCultureIgnoreCase))) 246 | { 247 | throw new Exception($"Missing cookie {cookieName}"); 248 | } 249 | } 250 | 251 | // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local 252 | private static void ValidateResponseMessage(HttpResponseMessage responseMessage, string errorMessage) 253 | { 254 | if (!responseMessage.IsSuccessStatusCode) 255 | { 256 | throw new Exception(errorMessage); 257 | } 258 | } 259 | 260 | private void ValidateModernTicketUrlResponseMessage(HttpResponseMessage responseMessage, string error) 261 | { 262 | if (!responseMessage.IsSuccessStatusCode && !responseMessage.StatusCode.Equals(HttpStatusCode.MovedPermanently)) 263 | { 264 | throw new Exception(error); 265 | } 266 | } 267 | 268 | /// 269 | /// 270 | /// Downloads the activity file. 271 | /// 272 | /// The activity identifier. 273 | /// The file format. 274 | /// 275 | /// Stream 276 | /// 277 | public async Task DownloadActivityFile(long activityId, ActivityFileTypeEnum fileFormat) 278 | { 279 | var url = fileFormat == DefaultFile 280 | ? string.Format(UrlActivityDownloadDefaultFile, activityId) 281 | : string.Format(UrlActivityDownloadFile, fileFormat.ToString().ToLower(), activityId); 282 | 283 | Stream streamCopy = new MemoryStream(); 284 | using (var res = await this.httpClient.GetAsync(url)) 285 | { 286 | await (await res.Content.ReadAsStreamAsync()).CopyToAsync(streamCopy); 287 | return streamCopy; 288 | } 289 | } 290 | 291 | /// 292 | /// 293 | /// Uploads the activity. 294 | /// 295 | /// Name of the file. 296 | /// The file format. 297 | /// 298 | /// Tuple of result and activity id 299 | /// 300 | /// 301 | /// Failed to upload {fileName} 302 | /// or 303 | /// or 304 | /// Unknown error: {response.ToString()} 305 | /// 306 | public async Task<(bool Success, long ActivityId)> UploadActivity(string fileName, FileFormat fileFormat) 307 | { 308 | var extension = fileFormat.FormatKey; 309 | var url = $"{URL_UPLOAD}/.{extension}"; 310 | this.httpClient.DefaultRequestHeaders.Add(BaseHeader.Item1, BaseHeader.Item2); 311 | 312 | var form = new MultipartFormDataContent( 313 | $"------WebKitFormBoundary{DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)}"); 314 | 315 | using (var stream = new FileStream(fileName, FileMode.Open)) 316 | { 317 | using (var content = new StreamContent(stream)) 318 | { 319 | content.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") 320 | { 321 | Name = "file", 322 | FileName = Path.GetFileName(fileName), 323 | Size = stream.Length 324 | }; 325 | 326 | form.Headers.Add(BaseHeader.Item1, BaseHeader.Item2); 327 | form.Add(content, "file", Path.GetFileName(fileName)); 328 | using (var res = await this.httpClient.PostAsync(url, form)) 329 | { 330 | // HTTP Status can either be OK or Conflict 331 | if (!new HashSet 332 | {HttpStatusCode.OK, HttpStatusCode.Created, HttpStatusCode.Conflict} 333 | .Contains(res.StatusCode)) 334 | { 335 | if (res.StatusCode == HttpStatusCode.PreconditionFailed) 336 | { 337 | throw new Exception($"Failed to upload {fileName}"); 338 | } 339 | } 340 | 341 | var responseData = await res.Content.ReadAsStringAsync(); 342 | var response = JObject.Parse(responseData)["detailedImportResult"]; 343 | var successes = response["successes"]; 344 | if (successes.HasValues) 345 | { 346 | return (true, long.Parse(successes[0]["internalId"].ToString())); 347 | } 348 | 349 | var failures = response["failures"]; 350 | if (!failures.HasValues) 351 | { 352 | throw new Exception($"Unknown error: {response}"); 353 | } 354 | 355 | var messages = failures[0]["messages"]; 356 | var code = int.Parse(messages[0]["code"].ToString()); 357 | if (code == (int)HttpStatusCode.Accepted) 358 | { 359 | // Activity already exists 360 | return (false, long.Parse(messages[0]["internalId"].ToString())); 361 | } 362 | 363 | throw new Exception(messages.ToString()); 364 | } 365 | } 366 | } 367 | } 368 | 369 | /// 370 | /// 371 | /// Sets the name of the activity. 372 | /// 373 | /// The activity identifier. 374 | /// Name of the activity. 375 | /// 376 | /// The task 377 | /// 378 | public async Task SetActivityName(long activityId, string activityName) 379 | { 380 | var url = $"{URL_ACTIVITY_BASE}/{activityId}"; 381 | this.httpClient.DefaultRequestHeaders.Add(BaseHeader.Item1, BaseHeader.Item2); 382 | this.httpClient.DefaultRequestHeaders.Add("X-HTTP-Method-Override", "PUT"); 383 | 384 | var data = new 385 | { 386 | activityId, 387 | activityName 388 | }; 389 | 390 | using (var res = await this.httpClient.PostAsync(url, 391 | new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json"))) 392 | { 393 | if (!res.IsSuccessStatusCode) 394 | { 395 | throw new Exception($"Activity name not set: {await res.Content.ReadAsStringAsync()}"); 396 | } 397 | } 398 | } 399 | 400 | /// 401 | /// 402 | /// Loads the activity types. 403 | /// 404 | /// 405 | /// List of activities 406 | /// 407 | public async Task> LoadActivityTypes() 408 | { 409 | return await this.ExecuteUrlGetRequest>(UrlActivityTypes, 410 | "Error while getting activity types"); 411 | } 412 | 413 | /// 414 | /// Loads the event types. 415 | /// 416 | /// 417 | public async Task> LoadEventTypes() 418 | { 419 | return await this.ExecuteUrlGetRequest>(UrlEventTypes, 420 | "Error while getting event types"); 421 | } 422 | 423 | /// 424 | /// 425 | /// Sets the type of the activity. 426 | /// 427 | /// The activity identifier. 428 | /// Type of the activity. 429 | /// 430 | /// The task 431 | /// 432 | public async Task SetActivityType(long activityId, ActivityType activityType) 433 | { 434 | var url = $"{URL_ACTIVITY_BASE}/{activityId}"; 435 | this.httpClient.DefaultRequestHeaders.Add(BaseHeader.Item1, BaseHeader.Item2); 436 | 437 | this.httpClient.DefaultRequestHeaders.Add("X-HTTP-Method-Override", "PUT"); 438 | 439 | var data = new 440 | { 441 | activityId, 442 | activityTypeDTO = activityType 443 | }; 444 | 445 | using (var res = await this.httpClient.PostAsync(url, 446 | new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json"))) 447 | { 448 | if (!res.IsSuccessStatusCode) 449 | { 450 | throw new Exception($"Activity type not set: {await res.Content.ReadAsStringAsync()}"); 451 | } 452 | } 453 | } 454 | 455 | /// 456 | /// Sets the type of the event. 457 | /// 458 | /// The activity identifier. 459 | /// Type of the event. 460 | /// 461 | public async Task SetEventType(long activityId, ActivityType eventType) 462 | { 463 | var url = $"{URL_ACTIVITY_BASE}/{activityId}"; 464 | this.httpClient.DefaultRequestHeaders.Add(BaseHeader.Item1, BaseHeader.Item2); 465 | 466 | this.httpClient.DefaultRequestHeaders.Add("X-HTTP-Method-Override", "PUT"); 467 | 468 | var data = new 469 | { 470 | activityId, 471 | eventTypeDTO = eventType 472 | }; 473 | 474 | using (var res = await this.httpClient.PostAsync(url, 475 | new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json"))) 476 | { 477 | if (!res.IsSuccessStatusCode) 478 | { 479 | throw new Exception($"Event type not set: {await res.Content.ReadAsStringAsync()}"); 480 | } 481 | } 482 | } 483 | 484 | /// 485 | /// 486 | /// Sets the activity description. 487 | /// 488 | /// The activity identifier. 489 | /// The description. 490 | /// 491 | /// The task 492 | /// 493 | public async Task SetActivityDescription(long activityId, string description) 494 | { 495 | var url = $"{URL_ACTIVITY_BASE}/{activityId}"; 496 | this.httpClient.DefaultRequestHeaders.Add(BaseHeader.Item1, BaseHeader.Item2); 497 | 498 | this.httpClient.DefaultRequestHeaders.Add("X-HTTP-Method-Override", "PUT"); 499 | 500 | var data = new 501 | { 502 | activityId, 503 | description 504 | }; 505 | 506 | using (var res = await this.httpClient.PostAsync(url, 507 | new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json"))) 508 | { 509 | if (!res.IsSuccessStatusCode) 510 | { 511 | throw new Exception($"Activity description not set: {await res.Content.ReadAsStringAsync()}"); 512 | } 513 | } 514 | } 515 | 516 | /// 517 | /// 518 | /// Loads the activity. 519 | /// 520 | /// The activity identifier. 521 | /// 522 | /// Activity 523 | /// 524 | public async Task LoadActivity(long activityId) 525 | { 526 | var url = $"{URL_ACTIVITY_BASE}/{activityId}"; 527 | this.httpClient.DefaultRequestHeaders.Add(BaseHeader.Item1, BaseHeader.Item2); 528 | 529 | return await this.ExecuteUrlGetRequest(url, "Error while getting activity"); 530 | } 531 | 532 | /// 533 | /// Gets the unix timestamp. 534 | /// 535 | /// The date. 536 | /// 537 | private static int GetUnixTimestamp(DateTime date) 538 | { 539 | return (int)date.Subtract(new DateTime(1970, 1, 1)).TotalSeconds; 540 | } 541 | 542 | /// 543 | /// Creates the activities URL. 544 | /// 545 | /// The limit. 546 | /// The start. 547 | /// The date. 548 | /// 549 | private static string CreateActivitiesUrl(int limit, int start, DateTime date) 550 | { 551 | return $"{UrlActivitiesBase}?limit={limit}&start={start}&_={GetUnixTimestamp(date)}"; 552 | } 553 | 554 | /// 555 | /// 556 | /// Loads the activities. 557 | /// 558 | /// The limit. 559 | /// The start. 560 | /// From. 561 | /// 562 | /// List of activities 563 | /// 564 | public async Task> LoadActivities(int limit, int start, DateTime from) 565 | { 566 | var url = CreateActivitiesUrl(limit, start, from); 567 | this.httpClient.DefaultRequestHeaders.Add(BaseHeader.Item1, BaseHeader.Item2); 568 | 569 | return await this.ExecuteUrlGetRequest>(url, "Error while getting activities"); 570 | } 571 | 572 | private static T DeserializeData(string data) where T : class 573 | { 574 | return typeof(T) == typeof(string) ? data as T : JsonConvert.DeserializeObject(data); 575 | } 576 | 577 | /// 578 | /// Executes the URL get request. 579 | /// 580 | /// 581 | /// The URL. 582 | /// The error message. 583 | /// 584 | private async Task ExecuteUrlGetRequest(string url, string errorMessage) where T : class 585 | { 586 | var res = await this.httpClient.GetAsync(url); 587 | var data = await res.Content.ReadAsStringAsync(); 588 | if (!res.IsSuccessStatusCode) 589 | { 590 | throw new Exception($"{errorMessage}: {data}"); 591 | } 592 | 593 | return DeserializeData(data); 594 | } 595 | 596 | /// 597 | /// Finalizes an instance of the class. 598 | /// 599 | ~Client() 600 | { 601 | if (this.httpClient == null) 602 | { 603 | return; 604 | } 605 | 606 | this.httpClient.Dispose(); 607 | this.httpClient = null; 608 | } 609 | } 610 | } 611 | -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Services/CloudStorage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.WindowsAzure.Storage; 2 | using Microsoft.WindowsAzure.Storage.Blob; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.IO; 5 | using System.Threading.Tasks; 6 | 7 | namespace GarminConnectClient.Lib.Services 8 | { 9 | /// 10 | /// 11 | /// Azure Cloud Storage 12 | /// 13 | /// 14 | [SuppressMessage("ReSharper", "UnusedMember.Global")] 15 | public class CloudStorage : IStorage 16 | { 17 | /// 18 | /// The Blob client 19 | /// 20 | private readonly CloudBlobClient blobClient; 21 | 22 | /// 23 | /// The main container name 24 | /// 25 | private readonly string mainContainerName; 26 | 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | /// The storage connection string. 31 | /// Name of the main container. 32 | public CloudStorage(string storageConnectionString, string mainContainerName) 33 | { 34 | var storageAccount = CloudStorageAccount.Parse(storageConnectionString); 35 | this.blobClient = storageAccount.CreateCloudBlobClient(); 36 | this.mainContainerName = mainContainerName; 37 | } 38 | 39 | /// 40 | /// 41 | /// Creates the directory. 42 | /// 43 | /// Name of the directory. 44 | public void CreateDirectory(string directoryName) 45 | { 46 | } 47 | 48 | /// 49 | /// 50 | /// Files the exists. 51 | /// 52 | /// Name of the file. 53 | /// 54 | /// Task with operation result 55 | /// 56 | public async Task FileExists(string fileName) 57 | { 58 | var blob = await this.GetBlobOfContainer(this.mainContainerName, fileName); 59 | var exists = await blob.ExistsAsync(); 60 | return exists; 61 | } 62 | 63 | /// 64 | /// 65 | /// Loads the data. 66 | /// 67 | /// Name of the file. 68 | /// 69 | /// Data string 70 | /// 71 | public async Task LoadData(string fileName) 72 | { 73 | var blob = await this.GetBlobOfContainer(this.mainContainerName, fileName); 74 | var data = await blob.DownloadTextAsync(); 75 | return data; 76 | } 77 | 78 | /// 79 | /// 80 | /// Stores the data. 81 | /// 82 | /// Name of the file. 83 | /// The data. 84 | /// 85 | /// Task result 86 | /// 87 | public async Task StoreData(string fileName, Stream data) 88 | { 89 | var blob = await this.GetBlobOfContainer(this.mainContainerName, fileName); 90 | data.Position = 0; 91 | await blob.UploadFromStreamAsync(data); 92 | } 93 | 94 | /// 95 | /// 96 | /// Stores the data. 97 | /// 98 | /// Name of the file. 99 | /// The data. 100 | /// 101 | /// Task result 102 | /// 103 | public async Task StoreData(string fileName, string data) 104 | { 105 | var blob = await this.GetBlobOfContainer(this.mainContainerName, fileName); 106 | await blob.UploadTextAsync(data); 107 | } 108 | 109 | /// 110 | /// 111 | /// Stores the data. 112 | /// 113 | /// Name of the file. 114 | /// The data. 115 | /// 116 | /// Task result 117 | /// 118 | public async Task StoreData(string fileName, byte[] data) 119 | { 120 | var blob = await this.GetBlobOfContainer(this.mainContainerName, fileName); 121 | await blob.UploadFromByteArrayAsync(data, 0, data.Length); 122 | } 123 | 124 | /// 125 | /// Gets the Blob of container. 126 | /// 127 | /// Name of the container. 128 | /// Name of the BLOB. 129 | /// 130 | private async Task GetBlobOfContainer(string containerName, string blobName) 131 | { 132 | var container = await this.GetContainer(containerName); 133 | return container.GetBlockBlobReference(blobName); 134 | } 135 | 136 | /// 137 | /// Gets the container. 138 | /// 139 | /// Name of the container. 140 | /// 141 | private async Task GetContainer(string containerName) 142 | { 143 | var container = this.blobClient.GetContainerReference(containerName); 144 | await container.CreateIfNotExistsAsync(); 145 | await container.SetPermissionsAsync(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Blob }); 146 | return container; 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Services/Downloader.cs: -------------------------------------------------------------------------------- 1 | using GarminConnectClient.Lib.Dto; 2 | using Microsoft.Extensions.Logging; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Net.Http; 9 | using System.Threading.Tasks; 10 | using GarminConnectClient.Lib.Enum; 11 | 12 | namespace GarminConnectClient.Lib.Services 13 | { 14 | /// 15 | /// 16 | /// Garmin Connect downloader 17 | /// 18 | /// 19 | public class Downloader : IDownloader 20 | { 21 | // ReSharper disable once MemberCanBePrivate.Global 22 | /// 23 | /// The activity data file 24 | /// 25 | public const string ActivityDataFile = "activity_data.json"; 26 | 27 | // ReSharper disable once MemberCanBePrivate.Global 28 | /// 29 | /// The activity data file 30 | /// 31 | public const string GpsDataFile = "gps_data"; 32 | 33 | /// 34 | /// The configuration 35 | /// 36 | private readonly IConfiguration configuration; 37 | 38 | /// 39 | /// The logger 40 | /// 41 | private readonly ILogger logger; 42 | 43 | /// 44 | /// The client 45 | /// 46 | private readonly IClient client; 47 | 48 | /// 49 | /// The storage 50 | /// 51 | private readonly IStorage storage; 52 | 53 | /// 54 | /// Initializes a new instance of the class. 55 | /// 56 | /// The configuration. 57 | /// The client. 58 | /// The storage. 59 | /// The logger. 60 | public Downloader(IConfiguration configuration, IClient client, IStorage storage, ILogger logger) 61 | { 62 | this.configuration = configuration; 63 | this.client = client; 64 | this.storage = storage; 65 | this.logger = logger; 66 | } 67 | 68 | /// 69 | /// 70 | /// Downloads the last user activities. 71 | /// 72 | /// List of activities 73 | public async Task> DownloadLastUserActivities() 74 | { 75 | const int limit = 50; 76 | var from = DateTime.UtcNow; 77 | await this.client.Authenticate(); 78 | var activities = await this.client.LoadActivities(limit, 0, from); 79 | var allActivities = new List(); 80 | 81 | while (activities.Any() && allActivities.Count < limit) 82 | { 83 | foreach (var activity in activities) 84 | { 85 | var downloadedActivity = await this.DownloadActivity(activity.ActivityId); 86 | if (downloadedActivity == null) 87 | { 88 | return allActivities; 89 | } 90 | allActivities.Add(downloadedActivity); 91 | } 92 | 93 | var firstItem = allActivities.OrderBy(e => e.Summary.StartTimeGmt).FirstOrDefault(); 94 | if (firstItem != null) 95 | { 96 | from = firstItem.Summary.StartTimeGmt.UtcDateTime; 97 | } 98 | 99 | activities = await this.client.LoadActivities(limit, 0, from); 100 | } 101 | 102 | return allActivities; 103 | } 104 | 105 | /// 106 | /// 107 | /// Gets the and store GPS data. 108 | /// 109 | /// The activity identifier. 110 | /// File format. 111 | /// Name of the file. 112 | /// Task result 113 | public async Task GetAndStoreGpsData(long activityId, ActivityFileTypeEnum fileFormat, 114 | string fileName) 115 | { 116 | this.logger.LogInformation($"Downloading of GPS data in {fileFormat.ToString()} format started."); 117 | using (var data = await this.client.DownloadActivityFile(activityId, fileFormat)) 118 | { 119 | //data.Position = 0; 120 | await this.storage.StoreData(fileName, data); 121 | } 122 | 123 | this.logger.LogInformation($"Downloading of GPS data in {fileFormat.ToString()} format done."); 124 | } 125 | 126 | /// 127 | /// Creates the name of the file. 128 | /// 129 | /// The root dir. 130 | /// Name of the file. 131 | /// File name 132 | private static string CreateFileName(string rootDir, string fileName) => Path.Combine(rootDir, fileName); 133 | 134 | /// 135 | /// 136 | /// Creates the name of the GPS file map. 137 | /// 138 | /// The export format. 139 | /// GPS file name 140 | public string CreateGpsFileMapName(ActivityFileTypeEnum exportFormat) => 141 | $"{GpsDataFile}.{(exportFormat == ActivityFileTypeEnum.Fit ? "zip" : exportFormat.ToString().ToLower())}"; 142 | 143 | /// 144 | /// 145 | /// Downloads the activity. 146 | /// 147 | /// Activity. 148 | /// Should be file existence checked. 149 | /// Move instance 150 | public async Task DownloadActivity(Activity activity, bool checkFileExistence) 151 | { 152 | var activityDir = string.IsNullOrWhiteSpace(this.configuration.BackupDir) ? activity.ActivityId.ToString() : Path.Combine(this.configuration.BackupDir, activity.ActivityId.ToString()); 153 | var activityDataFile = CreateFileName(activityDir, ActivityDataFile); 154 | 155 | if (checkFileExistence) 156 | { 157 | if (await this.storage.FileExists(activityDataFile)) 158 | { 159 | this.logger.LogInformation($"Activity {activity.ActivityId} is already downloaded."); 160 | return null; 161 | } 162 | } 163 | 164 | var mediaDir = Path.Combine(activityDir, "media"); 165 | 166 | this.storage.CreateDirectory(activityDir); 167 | this.storage.CreateDirectory(mediaDir); 168 | 169 | this.logger.LogInformation("Saving of activity data started."); 170 | await this.storage.StoreData(activityDataFile, JsonConvert.SerializeObject(activity)); 171 | this.logger.LogInformation("Saving of activity data done."); 172 | 173 | this.logger.LogInformation("Saving of activity media started."); 174 | foreach (var image in activity.Metadata.ActivityImages ?? new ActivityImage[0]) 175 | { 176 | using (var httpClient = new HttpClient()) 177 | { 178 | var data = await httpClient.GetByteArrayAsync(image.Url); 179 | var urlParts = image.Url.ToString().Split('/'); 180 | await this.storage.StoreData(CreateFileName(mediaDir, urlParts[urlParts.Length - 1]), data); 181 | } 182 | } 183 | this.logger.LogInformation("Saving of activity media done."); 184 | 185 | foreach (var exportFormat in new[] { 186 | ActivityFileTypeEnum.Kml, 187 | ActivityFileTypeEnum.Gpx, 188 | ActivityFileTypeEnum.Csv, 189 | ActivityFileTypeEnum.Fit, 190 | ActivityFileTypeEnum.Tcx 191 | }) 192 | { 193 | await this.GetAndStoreGpsData(activity.ActivityId, exportFormat, CreateFileName(activityDir, this.CreateGpsFileMapName(exportFormat))); 194 | } 195 | 196 | this.logger.LogInformation($"Downloading of activity {activity.ActivityId} done."); 197 | return activity; 198 | } 199 | 200 | /// 201 | /// 202 | /// Downloads the activity. 203 | /// 204 | /// The activity identifier. 205 | /// 206 | /// Activity instance 207 | /// 208 | public async Task DownloadActivity(long activityId) 209 | { 210 | this.logger.LogInformation($"Downloading of activity {activityId} started."); 211 | var activityDir = string.IsNullOrWhiteSpace(this.configuration.BackupDir) ? activityId.ToString() : Path.Combine(this.configuration.BackupDir, activityId.ToString()); 212 | var activityDataFile = CreateFileName(activityDir, ActivityDataFile); 213 | 214 | if (await this.storage.FileExists(activityDataFile)) 215 | { 216 | this.logger.LogInformation($"Activity {activityId} is already downloaded."); 217 | return null; 218 | } 219 | 220 | this.logger.LogInformation("Saving of activity data started."); 221 | var activity = await this.client.LoadActivity(activityId); 222 | return await this.DownloadActivity(activity, false); 223 | } 224 | } 225 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Services/IClient.cs: -------------------------------------------------------------------------------- 1 | using GarminConnectClient.Lib.Dto; 2 | using GarminConnectClient.Lib.Enum; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Net; 7 | using System.Net.Http; 8 | using System.Threading.Tasks; 9 | 10 | namespace GarminConnectClient.Lib.Services 11 | { 12 | /// 13 | /// IClient 14 | /// Inspired by https://github.com/La0/garmin-uploader 15 | /// 16 | public interface IClient 17 | { 18 | /// 19 | /// Authenticates this instance. 20 | /// 21 | /// Tuple of Cookies and HTTP handler 22 | Task<(CookieContainer, HttpClientHandler)> Authenticate(); 23 | 24 | /// 25 | /// Downloads the activity file. 26 | /// 27 | /// The activity identifier. 28 | /// The file format. 29 | /// Stream 30 | Task DownloadActivityFile(long activityId, ActivityFileTypeEnum fileFormat); 31 | 32 | /// 33 | /// Loads the activities. 34 | /// 35 | /// The limit. 36 | /// The start. 37 | /// From. 38 | /// List of activities 39 | Task> LoadActivities(int limit, int start, DateTime from); 40 | 41 | /// 42 | /// Loads the activity. 43 | /// 44 | /// The activity identifier. 45 | /// Activity 46 | Task LoadActivity(long activityId); 47 | 48 | /// 49 | /// Loads the activity types. 50 | /// 51 | /// List of activities 52 | Task> LoadActivityTypes(); 53 | 54 | /// 55 | /// Sets the activity description. 56 | /// 57 | /// The activity identifier. 58 | /// The description. 59 | /// The task 60 | Task SetActivityDescription(long activityId, string description); 61 | 62 | /// 63 | /// Sets the name of the activity. 64 | /// 65 | /// The activity identifier. 66 | /// Name of the activity. 67 | /// The task 68 | Task SetActivityName(long activityId, string activityName); 69 | 70 | /// 71 | /// Sets the type of the activity. 72 | /// 73 | /// The activity identifier. 74 | /// Type of the activity. 75 | /// The task 76 | Task SetActivityType(long activityId, ActivityType activityType); 77 | 78 | /// 79 | /// Uploads the activity. 80 | /// 81 | /// Name of the file. 82 | /// The file format. 83 | /// Tuple of result and activity id 84 | Task<(bool Success, long ActivityId)> UploadActivity(string fileName, FileFormat fileFormat); 85 | } 86 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Services/IDownloader.cs: -------------------------------------------------------------------------------- 1 | using GarminConnectClient.Lib.Dto; 2 | using GarminConnectClient.Lib.Enum; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | namespace GarminConnectClient.Lib.Services 7 | { 8 | /// 9 | /// Data downloader. 10 | /// 11 | public interface IDownloader 12 | { 13 | /// 14 | /// Downloads the last user activities. 15 | /// 16 | /// List of activities 17 | Task> DownloadLastUserActivities(); 18 | 19 | /// 20 | /// Gets the and store GPS data. 21 | /// 22 | /// The activity identifier. 23 | /// The export format. 24 | /// Name of the file. 25 | /// Task result 26 | Task GetAndStoreGpsData(long activityId, ActivityFileTypeEnum exportFormat, 27 | string fileName); 28 | 29 | /// 30 | /// Downloads the activity. 31 | /// 32 | /// The activity instance. 33 | /// Should check activity directory existence. 34 | /// Activity instance 35 | Task DownloadActivity(Activity activity, bool checkFileExistence); 36 | 37 | /// 38 | /// Downloads the activity. 39 | /// 40 | /// The activity identifier. 41 | /// Activity instance 42 | Task DownloadActivity(long activityId); 43 | 44 | /// 45 | /// Creates the name of the GPS file map. 46 | /// 47 | /// The export format. 48 | /// GPS file name 49 | string CreateGpsFileMapName(ActivityFileTypeEnum exportFormat); 50 | } 51 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/Services/IStorage.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | 4 | namespace GarminConnectClient.Lib.Services 5 | { 6 | /// 7 | /// IStorage interface 8 | /// 9 | public interface IStorage 10 | { 11 | // ReSharper disable once UnusedMember.Global 12 | /// 13 | /// Loads the data. 14 | /// 15 | /// Name of the file. 16 | /// Data string 17 | Task LoadData(string fileName); 18 | 19 | /// 20 | /// Stores the data. 21 | /// 22 | /// Name of the file. 23 | /// The data. 24 | /// Task result 25 | Task StoreData(string fileName, Stream data); 26 | 27 | /// 28 | /// Stores the data. 29 | /// 30 | /// Name of the file. 31 | /// The data. 32 | /// Task result 33 | Task StoreData(string fileName, string data); 34 | 35 | /// 36 | /// Stores the data. 37 | /// 38 | /// Name of the file. 39 | /// The data. 40 | /// Task result 41 | Task StoreData(string fileName, byte[] data); 42 | 43 | /// 44 | /// Files the exists. 45 | /// 46 | /// Name of the file. 47 | /// Task with operation result 48 | Task FileExists(string fileName); 49 | 50 | /// 51 | /// Creates the directory. 52 | /// 53 | /// Name of the directory. 54 | // ReSharper disable once UnusedParameter.Global 55 | void CreateDirectory(string directoryName); 56 | } 57 | } -------------------------------------------------------------------------------- /GarminConnectClient.Lib/scripts/build_and_publish.ps1: -------------------------------------------------------------------------------- 1 | # This script uses configuration from NuGet.config file, see https://docs.microsoft.com/cs-cz/nuget/reference/cli-reference/cli-ref-setapikey. 2 | # To add proper API key for source, use command (sample for Nuget.org) `nuget setapikey API_KEY -Source https://www.nuget.org/api/v2/package`. 3 | 4 | $Publishing = @( 5 | @{ 6 | Source="nuget.org" 7 | } 8 | ) 9 | 10 | Write-Host "Building project." 11 | dotnet build --configuration Release 12 | 13 | Write-Host "Creating NuGet package." 14 | dotnet pack --configuration Release 15 | 16 | $releaseDir = "./bin/Release" 17 | $NewestPackage = Get-ChildItem -Path $releaseDir -Filter "*.nupkg" | Sort-Object LastAccessTime -Descending | Select-Object -First 1 18 | 19 | foreach ($Target in $Publishing) { 20 | Write-Host "Publishing NuGet package $($NewestPackage.name) to $($Target.Source)." 21 | dotnet nuget push "$releaseDir/$($NewestPackage.name)" --source $($Target.Source) 22 | } 23 | -------------------------------------------------------------------------------- /GarminConnectClient.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28010.2026 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GarminConnectClient.Console", "GarminConnectClient.Console\GarminConnectClient.Console.csproj", "{0D427C02-5218-476C-8218-D86F874FF968}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GarminConnectClient.Lib", "GarminConnectClient.Lib\GarminConnectClient.Lib.csproj", "{F0A2F738-C8AE-4528-A1C3-C8F363D370A2}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {0D427C02-5218-476C-8218-D86F874FF968}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {0D427C02-5218-476C-8218-D86F874FF968}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {0D427C02-5218-476C-8218-D86F874FF968}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {0D427C02-5218-476C-8218-D86F874FF968}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {F0A2F738-C8AE-4528-A1C3-C8F363D370A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {F0A2F738-C8AE-4528-A1C3-C8F363D370A2}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {F0A2F738-C8AE-4528-A1C3-C8F363D370A2}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {F0A2F738-C8AE-4528-A1C3-C8F363D370A2}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {967C6126-3C9B-4A31-B726-3C339D51A3E7} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /GarminConnectClient.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Marek Polak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Garmin Connect Client 4 | 5 | GarminConnectClient is a library for communication with **GarminConnect**. It allows to list, download and upload Garmin activities. 6 | It is inspired by [https://github.com/La0/garmin-uploader](https://github.com/La0/garmin-uploader). 7 | 8 | ## Version 9 | 10 | - **Version 1.2.0** - 2020-01-12 11 | 12 | - Fix of the GarminConnect login workflow. Thanks [@GroveJay](https://github.com/GroveJay). 13 | 14 | - **Version 1.1.0** - 2019-05-21 15 | 16 | - Fix of the workflow. A csrf token must be parsed and added to the login step. 17 | 18 | - **Version 1.0.1** - 2018-11-29 19 | - Initial version. 20 | 21 | ### Getting Started/Installing 22 | 23 | ```ps 24 | PM> Install-Package GarminConnectClientNet -Version 1.1.0 25 | ``` 26 | 27 | ## Project Description 28 | 29 | The solution consists of the following projects: 30 | 31 | - **GarminConnectClient.Lib** is the main library containing client. 32 | 33 | - **GarminConnectClient.Console** is just a sample that uploads *Movescount* moves data, to *GarminConnect*. 34 | 35 | - **GarminConnectClient.Lib.Spec** TODO. 36 | 37 | 38 | ### Prerequisites 39 | 40 | - .NET Core 2.0. 41 | 42 | 43 | ### Configuration 44 | 45 | #### GarminConnectClient.Lib 46 | 47 | - **Username** - Garmin Connect username. 48 | - **Password** - Garmin Connect password. 49 | - **BackupDir** - Name of backup directory. 50 | - **StorageConnectionString** - Connection string of Azure Storage, needed by CloudStorage class. Optional. 51 | - **ContainerName** - Name of Azure Storage container, needed by CloudStorage class. Optional. 52 | 53 | #### GarminConnectClient.Console 54 | 55 | - **Username** - Garmin Connect username. 56 | - **Password** - Garmin Connect password. 57 | - **BackupDir** - Name of backup directory. 58 | - **StorageConnectionString** - Connection string of Azure Storage, needed by CloudStorage class. Optional. 59 | - **ContainerName** - Name of Azure Storage container, needed by CloudStorage class. Optional. 60 | 61 | 62 | ## Deployment 63 | 64 | - Just install package by NuGet. 65 | 66 | 67 | ## Contributing 68 | 69 | Any contribution is welcomed. 70 | 71 | ## Authors 72 | 73 | - **Marek Polak** - *Initial work* - [marazt](https://github.com/marazt) 74 | 75 | ## License 76 | 77 | © 2018 Marek Polak. This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details. 78 | 79 | ## Acknowledgments 80 | 81 | - Enjoy it! 82 | - If you want, you can support this project too. -------------------------------------------------------------------------------- /logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marazt/garmin-connect-client/31e60ab62ba6f63b232d1ed4cd7d847040926a6c/logo-128.png -------------------------------------------------------------------------------- /logo-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marazt/garmin-connect-client/31e60ab62ba6f63b232d1ed4cd7d847040926a6c/logo-256.png -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marazt/garmin-connect-client/31e60ab62ba6f63b232d1ed4cd7d847040926a6c/logo.png --------------------------------------------------------------------------------