diff --git a/Fitbit/Fitbit-WithSamples.sln b/Fitbit/Fitbit-WithSamples.sln
index 679b14a1..2ad90244 100644
--- a/Fitbit/Fitbit-WithSamples.sln
+++ b/Fitbit/Fitbit-WithSamples.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.24720.0
+VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{C284DD15-3355-4C6A-9EE0-D0A3BEA4D8A5}"
ProjectSection(SolutionItems) = preProject
@@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebMVC.Portable", "..
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebMVCOAuth2", "..\SampleWebMVCOAuth2\SampleWebMVCOAuth2.csproj", "{61C8AD29-A128-442E-BF0B-539A990B15C7}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleConsole", "..\SampleConsole\SampleConsole\SampleConsole.csproj", "{11886EF6-6527-4061-A859-3191F7E3B192}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -58,6 +60,12 @@ Global
{61C8AD29-A128-442E-BF0B-539A990B15C7}.Release|Any CPU.Build.0 = Release|Any CPU
{61C8AD29-A128-442E-BF0B-539A990B15C7}.TrialRelease|Any CPU.ActiveCfg = Release|Any CPU
{61C8AD29-A128-442E-BF0B-539A990B15C7}.TrialRelease|Any CPU.Build.0 = Release|Any CPU
+ {11886EF6-6527-4061-A859-3191F7E3B192}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {11886EF6-6527-4061-A859-3191F7E3B192}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {11886EF6-6527-4061-A859-3191F7E3B192}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {11886EF6-6527-4061-A859-3191F7E3B192}.Release|Any CPU.Build.0 = Release|Any CPU
+ {11886EF6-6527-4061-A859-3191F7E3B192}.TrialRelease|Any CPU.ActiveCfg = Release|Any CPU
+ {11886EF6-6527-4061-A859-3191F7E3B192}.TrialRelease|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/SampleConsole/SampleConsole/App.config b/SampleConsole/SampleConsole/App.config
new file mode 100644
index 00000000..13447893
--- /dev/null
+++ b/SampleConsole/SampleConsole/App.config
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SampleConsole/SampleConsole/AuthorizationHelper.cs b/SampleConsole/SampleConsole/AuthorizationHelper.cs
new file mode 100644
index 00000000..0803490f
--- /dev/null
+++ b/SampleConsole/SampleConsole/AuthorizationHelper.cs
@@ -0,0 +1,143 @@
+using Fitbit.Api.Portable;
+using Fitbit.Api.Portable.OAuth2;
+using OutputColorizer;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Security.Principal;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Serialization;
+
+namespace SampleConsole
+{
+ public static class AuthorizationHelper
+ {
+ public static FitbitClient GetAuthorizedFitBitClient(params string[] scopes)
+ {
+ // try to retrieve the token from disk
+ OAuth2AccessToken token = GetAccessTokenAsync(scopes).Result;
+
+ return new FitbitClient(new FitbitAppCredentials() { ClientId = Options.ClientId, ClientSecret = Options.ClientSecret }, token, true);
+ }
+
+ public static async Task GetAccessTokenAsync(params string[] scopes)
+ {
+ var token = ReadTokenFromDisk();
+ if (token == null || token.UtcExpirationDate < DateTime.Now.ToUniversalTime())
+ {
+ // we need admin to retrieve the token automatically
+ RestartAsElevatedIfNeeded();
+
+ token = await AuthorizeToFitBitAsync(scopes.Length == 0 ? Options.AllScopes : scopes);
+
+ SaveTokenToFile(token);
+ }
+
+ return token;
+ }
+
+ private static void SaveTokenToFile(OAuth2AccessToken token)
+ {
+ try
+ {
+ XmlSerializer ser = new XmlSerializer(typeof(OAuth2AccessToken));
+ using (StreamWriter sw = new StreamWriter("token.dat"))
+ {
+ ser.Serialize(sw, token);
+ }
+ }
+ catch
+ {
+ }
+ }
+
+ private static OAuth2AccessToken ReadTokenFromDisk()
+ {
+ if (!File.Exists("token.dat"))
+ {
+ return null;
+ }
+
+ try
+ {
+ XmlSerializer ser = new XmlSerializer(typeof(OAuth2AccessToken));
+ using (StreamReader sr = new StreamReader("token.dat"))
+ {
+ return ser.Deserialize(sr) as OAuth2AccessToken;
+ }
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ private static async Task AuthorizeToFitBitAsync(string[] scopes)
+ {
+ Colorizer.WriteLine("Sending authorization request...");
+ string scope = string.Join("%20", scopes);
+ string authorizeUrl = $"https://www.fitbit.com/oauth2/authorize?response_type=code&client_id={Options.ClientId}&scope={scope}&expires_in=86400";
+ Process.Start(authorizeUrl);
+
+ Colorizer.WriteLine("Waiting for callback at [Yellow!http://localhost]");
+ string code;
+ using (HttpListener listener = new HttpListener())
+ {
+ listener.Prefixes.Add("http://localhost/");
+ listener.Start();
+ HttpListenerContext context = listener.GetContext();
+
+ Colorizer.WriteLine("Request received, retrieving code");
+ HttpListenerRequest request = context.Request;
+
+ //retrieve the code from the raw request.
+ code = request.QueryString["code"];
+ }
+
+ Colorizer.WriteLine("Exchanging code for authentication token");
+ using (HttpClient hc = new HttpClient())
+ {
+ HttpRequestMessage requestMsg = new HttpRequestMessage();
+ requestMsg.Method = HttpMethod.Post;
+ requestMsg.RequestUri = new Uri("https://api.fitbit.com/oauth2/token");
+ string authorizationHeader = Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes($"{Options.ClientId}:{Options.ClientSecret}"));
+ requestMsg.Headers.Add("Authorization", "Basic " + authorizationHeader);
+ requestMsg.Content = new StringContent($"client_id={Options.ClientSecret}&grant_type=authorization_code&code={code}", Encoding.ASCII, "application/x-www-form-urlencoded");
+
+ Colorizer.Write("Making request...");
+ using (var responseMsg = await hc.SendAsync(requestMsg))
+ {
+ Colorizer.WriteLine("[Green!done].");
+ if (responseMsg.IsSuccessStatusCode)
+ {
+ var tok = OAuth2Helper.ParseAccessTokenResponse(await responseMsg.Content.ReadAsStringAsync());
+ tok.UtcExpirationDate = DateTime.Now.ToUniversalTime().AddSeconds(tok.ExpiresIn);
+
+ return tok;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private static void RestartAsElevatedIfNeeded()
+ {
+ // we can't authorize if we are not admin because we require access to register a listener to http://localhost.
+ if (!WindowsIdentity.GetCurrent().Owner.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid))
+ {
+ // start the same process (as admin)
+ Process elevatedProcess = new Process();
+ elevatedProcess.StartInfo.FileName = System.Reflection.Assembly.GetEntryAssembly().Location;
+ elevatedProcess.StartInfo.Arguments = string.Join(" ", Environment.GetCommandLineArgs()); // pass whatever arguments were passed before.
+ elevatedProcess.StartInfo.Verb = "runas"; //run as admin
+ elevatedProcess.Start();
+
+ Environment.Exit(0);
+ }
+ }
+ }
+}
diff --git a/SampleConsole/SampleConsole/Options.cs b/SampleConsole/SampleConsole/Options.cs
new file mode 100644
index 00000000..2c98cb3a
--- /dev/null
+++ b/SampleConsole/SampleConsole/Options.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleConsole
+{
+ public class Options
+ {
+ public static string ClientId = System.Configuration.ConfigurationManager.AppSettings["FitbitConsumerKey"];
+ public static string ClientSecret = System.Configuration.ConfigurationManager.AppSettings["FitbitConsumerSecret"];
+ public static string[] AllScopes = new string[] { "activity ", "nutrition ", "heartrate ", "location ", "nutrition ", "profile ", "settings ", "sleep ", "social ", "weight" };
+ }
+}
diff --git a/SampleConsole/SampleConsole/Program.cs b/SampleConsole/SampleConsole/Program.cs
new file mode 100644
index 00000000..a7fac9c9
--- /dev/null
+++ b/SampleConsole/SampleConsole/Program.cs
@@ -0,0 +1,31 @@
+using Fitbit.Api.Portable;
+using Fitbit.Models;
+using OutputColorizer;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace SampleConsole
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ // Authorize with FitBit
+ FitbitClient fc = AuthorizationHelper.GetAuthorizedFitBitClient("activity ", "nutrition ", "heartrate ", "location ", "nutrition ", "profile ", "settings ", "sleep ", "social ", "weight");
+
+ // Retrieve the weight information for today
+ DateTime start = DateTime.Now;
+ Colorizer.Write("Processing [Yellow!{0}]...", start.ToShortDateString());
+ var weight = fc.GetWeightAsync(start, DateRangePeriod.OneMonth).Result;
+ Colorizer.WriteLine("found [Green!{0}] entries.", weight.Weights.Count);
+
+ // Save the downloaded information to disk
+ System.Xml.Serialization.XmlSerializer src = new System.Xml.Serialization.XmlSerializer(typeof(List));
+ using (StreamWriter sw = new StreamWriter("weight.txt"))
+ {
+ src.Serialize(sw, weight);
+ }
+ }
+ }
+}
diff --git a/SampleConsole/SampleConsole/Properties/AssemblyInfo.cs b/SampleConsole/SampleConsole/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..aa390a5d
--- /dev/null
+++ b/SampleConsole/SampleConsole/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("SampleConsole")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SampleConsole")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("11886ef6-6527-4061-a859-3191f7e3b192")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/SampleConsole/SampleConsole/SampleConsole.csproj b/SampleConsole/SampleConsole/SampleConsole.csproj
new file mode 100644
index 00000000..ae03731f
--- /dev/null
+++ b/SampleConsole/SampleConsole/SampleConsole.csproj
@@ -0,0 +1,74 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {11886EF6-6527-4061-A859-3191F7E3B192}
+ Exe
+ Properties
+ SampleConsole
+ SampleConsole
+ v4.5.2
+ 512
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\..\Fitbit\packages\OutputColorizer.1.1.0\lib\net45\OutputColorizer.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {1358d3b4-0698-4003-97eb-b6d489e04138}
+ Fitbit.Portable
+
+
+
+
+
\ No newline at end of file
diff --git a/SampleConsole/SampleConsole/packages.config b/SampleConsole/SampleConsole/packages.config
new file mode 100644
index 00000000..9156353e
--- /dev/null
+++ b/SampleConsole/SampleConsole/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file