520 lines
17 KiB
C#
520 lines
17 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Xml;
|
|
using System.Xml.Linq;
|
|
using Newtonsoft.Json;
|
|
using UnityEngine;
|
|
|
|
namespace Assets.Scripts.Fitbit
|
|
{
|
|
|
|
public class FitBitAPI : MonoBehaviour
|
|
{
|
|
|
|
private const string _consumerSecret = "69307b9f332caf9946ef4e23cabde2e4";
|
|
private const string _clientId = "22CX4L";
|
|
private const string _callbackURL = "http://localhost/callback";
|
|
private const string CustomAndroidScheme = "http://localhost/callback";
|
|
|
|
private const string _tokenUrl = "https://api.fitbit.com/oauth2/token";
|
|
private const string _baseGetUrl = "https://api.fitbit.com/1/user/-/";
|
|
private const string _profileUrl = _baseGetUrl + "profile.json/";
|
|
private const string _activityUrl = _baseGetUrl + "activities/";
|
|
|
|
private string _distanceUrl = _activityUrl + "distance/date/" + _currentDateTime + "/1d.json";
|
|
|
|
private string _stepsUrl = _activityUrl + "steps/date/" + _currentDateTime + "/1d.json";
|
|
|
|
private string _caloriesUrl = _activityUrl + "calories/date/" + _currentDateTime + "/1d.json";
|
|
|
|
private string _sleepUrl = _baseGetUrl + "sleep/minutesAsleep/date/" + _currentDateTime + "/" + _currentDateTime + ".json";
|
|
|
|
private static string _currentDateTime = GetCurrentDate();
|
|
|
|
private string _returnCode;
|
|
private WWW _wwwRequest;
|
|
private bool _bGotTheData = false;
|
|
private bool _bFirstFire = true;
|
|
|
|
private OAuth2AccessToken _oAuth2 = new OAuth2AccessToken();
|
|
public FitbitData _fitbitData = new FitbitData();
|
|
|
|
private string _statusMessage;
|
|
|
|
private string CallBackUrl
|
|
{
|
|
get
|
|
{
|
|
//determine which platform we're running on and use the appropriate url
|
|
if (Application.platform == RuntimePlatform.WindowsEditor)
|
|
return WWW.EscapeURL(_callbackURL);
|
|
else if (Application.platform == RuntimePlatform.Android)
|
|
{
|
|
return WWW.EscapeURL(CustomAndroidScheme);
|
|
}
|
|
else
|
|
{
|
|
return WWW.EscapeURL(CustomAndroidScheme);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
DontDestroyOnLoad(this);
|
|
}
|
|
private void OnGUI()
|
|
{
|
|
if (!_bGotTheData && !string.IsNullOrEmpty(_statusMessage) && _bFirstFire)
|
|
{
|
|
_bFirstFire = false;
|
|
}
|
|
//GUI.Label(new Rect(10, 10, 100, 20), "Calories: " + _fitbitData.CurrentCalories);
|
|
}
|
|
|
|
public void LoginToFitbit()
|
|
{
|
|
|
|
if (PlayerPrefs.HasKey("FitbitRefreshToken"))
|
|
{
|
|
UseRefreshToken();
|
|
}
|
|
else
|
|
{
|
|
UserAcceptOrDeny();
|
|
}
|
|
|
|
}
|
|
public void UserAcceptOrDeny()
|
|
{
|
|
var url =
|
|
"https://www.fitbit.com/oauth2/authorize?response_type=code&client_id=" + _clientId + "&prompt=login&redirect_uri=" +
|
|
CallBackUrl +
|
|
"&scope=activity%20nutrition%20heartrate%20location%20profile%20sleep%20weight%20social";
|
|
Application.OpenURL(url);
|
|
// print(url);
|
|
#if UNITY_EDITOR
|
|
#endif
|
|
}
|
|
|
|
public void ClearRefreshCode()
|
|
{
|
|
PlayerPrefs.DeleteKey("FitbitRefreshToken");
|
|
Debug.Log("Refresh Token has been CLEARED!");
|
|
}
|
|
|
|
private void UseReturnCode()
|
|
{
|
|
Debug.Log("return code isn't empty");
|
|
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(_clientId + ":" + _consumerSecret);
|
|
var encoded = Convert.ToBase64String(plainTextBytes);
|
|
|
|
var form = new WWWForm();
|
|
form.AddField("client_id", _clientId);
|
|
form.AddField("grant_type", "authorization_code");
|
|
form.AddField("redirect_uri", WWW.UnEscapeURL(CallBackUrl));
|
|
form.AddField("code", _returnCode);
|
|
|
|
var headers = form.headers;
|
|
headers["Authorization"] = "Basic " + encoded;
|
|
|
|
_wwwRequest = new WWW(_tokenUrl, form.data, headers);
|
|
StartCoroutine(WaitForAccess(_wwwRequest));
|
|
|
|
while (!_wwwRequest.isDone)
|
|
{
|
|
}
|
|
|
|
Debug.Log("Token: " + _wwwRequest.text);
|
|
Debug.Log("parsing token");
|
|
|
|
var parsed = new JSONObject(_wwwRequest.text);
|
|
ParseAccessToken(parsed);
|
|
Debug.Log("\nParsed Token: " + _oAuth2.Token);
|
|
GetAllData();
|
|
Debug.Log("Steps from Fitbit: " + _fitbitData.CurrentSteps);
|
|
_bGotTheData = true;
|
|
}
|
|
|
|
public void UseRefreshToken()
|
|
{
|
|
Debug.Log("Using Refresh Token");
|
|
var plainTextBytes = Encoding.UTF8.GetBytes(_clientId + ":" + _consumerSecret);
|
|
var encoded = Convert.ToBase64String(plainTextBytes);
|
|
|
|
var form = new WWWForm();
|
|
form.AddField("grant_type", "refresh_token");
|
|
form.AddField("refresh_token", PlayerPrefs.GetString("FitbitRefreshToken"));
|
|
|
|
var headers = form.headers;
|
|
headers["Authorization"] = "Basic " + encoded;
|
|
|
|
_wwwRequest = new WWW(_tokenUrl, form.data, headers);
|
|
StartCoroutine(WaitForAccess(_wwwRequest));
|
|
while (!_wwwRequest.isDone)
|
|
{
|
|
}
|
|
|
|
Debug.Log("RefreshToken wwwText: " + _wwwRequest.text);
|
|
if (!String.IsNullOrEmpty(_wwwRequest.error))
|
|
{
|
|
PlayerPrefs.DeleteKey("FitbitRefreshToken");
|
|
UserAcceptOrDeny();
|
|
UseReturnCode();
|
|
GetAllData();
|
|
}
|
|
else
|
|
{
|
|
Debug.Log("Using the Auth Token (UseRefreshToken)");
|
|
var parsed = new JSONObject(_wwwRequest.text);
|
|
ParseAccessToken(parsed);
|
|
GetAllData();
|
|
}
|
|
}
|
|
|
|
public void SetReturnCodeFromAndroid(string code)
|
|
{
|
|
if (string.IsNullOrEmpty(code))
|
|
return;
|
|
_returnCode = code.Substring(CustomAndroidScheme.Length + 6);
|
|
Debug.Log("Return Code is: " + _returnCode);
|
|
UseReturnCode();
|
|
}
|
|
|
|
public void SetReturnCode(string code)
|
|
{
|
|
if (string.IsNullOrEmpty(code))
|
|
return;
|
|
|
|
_returnCode = code;
|
|
UseReturnCode();
|
|
}
|
|
|
|
public void GetAllData()
|
|
{
|
|
GetProfileData();
|
|
GetAllRelevantData();
|
|
BuildProfile();
|
|
_fitbitData.LastSyncTime = DateTime.Now.ToUniversalTime();
|
|
Debug.Log("LastSyncTime: " + DateTime.Now.ToUniversalTime().ToString("g"));
|
|
}
|
|
|
|
private void GetAllRelevantData()
|
|
{
|
|
GetSteps();
|
|
GetDistance();
|
|
GetCalories();
|
|
GetSleep();
|
|
}
|
|
|
|
#region GetData
|
|
private void GetProfileData()
|
|
{
|
|
|
|
//time for Getting Dataz
|
|
var headers = new Dictionary<string, string>();
|
|
headers["Authorization"] = "Bearer " + _oAuth2.Token;
|
|
|
|
_wwwRequest = new WWW(_profileUrl, null, headers);
|
|
Debug.Log("Doing GET Request");
|
|
StartCoroutine(WaitForAccess(_wwwRequest));
|
|
|
|
while (!_wwwRequest.isDone)
|
|
{
|
|
}
|
|
|
|
ParseProfileData(_wwwRequest.text);
|
|
|
|
}
|
|
|
|
private void GetCalories()
|
|
{
|
|
var headers = new Dictionary<string, string>();
|
|
headers["Authorization"] = "Bearer " + _oAuth2.Token;
|
|
|
|
Debug.Log("Calories URL is: " + _caloriesUrl);
|
|
_wwwRequest = new WWW(_caloriesUrl, null, headers);
|
|
Debug.Log("Doing Calories GET Request");
|
|
StartCoroutine(WaitForAccess(_wwwRequest));
|
|
|
|
while (!_wwwRequest.isDone)
|
|
{
|
|
}
|
|
ParseCaloriesData(_wwwRequest.text);
|
|
}
|
|
|
|
private void GetDistance()
|
|
{
|
|
var headers = new Dictionary<string, string>();
|
|
headers["Authorization"] = "Bearer " + _oAuth2.Token;
|
|
|
|
Debug.Log("Distance URL is: " + _distanceUrl);
|
|
_wwwRequest = new WWW(_distanceUrl, null, headers);
|
|
Debug.Log("Doing Distance GET Request");
|
|
StartCoroutine(WaitForAccess(_wwwRequest));
|
|
|
|
while (!_wwwRequest.isDone)
|
|
{
|
|
}
|
|
|
|
ParseDistanceData(_wwwRequest.text);
|
|
}
|
|
|
|
private void GetSteps()
|
|
{
|
|
var headers = new Dictionary<string, string>();
|
|
headers["Authorization"] = "Bearer " + _oAuth2.Token;
|
|
|
|
Debug.Log("Steps URL is: " + _stepsUrl);
|
|
_wwwRequest = new WWW(_stepsUrl, null, headers);
|
|
Debug.Log("Doing Steps GET Request");
|
|
StartCoroutine(WaitForAccess(_wwwRequest));
|
|
|
|
while (!_wwwRequest.isDone)
|
|
{
|
|
}
|
|
|
|
ParseStepsData(_wwwRequest.text);
|
|
}
|
|
|
|
private void GetSleep()
|
|
{
|
|
var headers = new Dictionary<string, string>();
|
|
headers["Authorization"] = "Bearer " + _oAuth2.Token;
|
|
|
|
Debug.Log("Sleep URL is: " + _sleepUrl);
|
|
_wwwRequest = new WWW(_sleepUrl, null, headers);
|
|
Debug.Log("Doing Sleep GET Request");
|
|
StartCoroutine(WaitForAccess(_wwwRequest));
|
|
|
|
//DIRTY DIRTY HACK
|
|
while (!_wwwRequest.isDone)
|
|
{
|
|
}
|
|
|
|
ParseSleepData(_wwwRequest.text);
|
|
}
|
|
|
|
private void BuildProfile()
|
|
{
|
|
var imageWWW = new WWW(_fitbitData.ProfileData["avatar"]);
|
|
//DIRTY DIRTY HACK
|
|
while (!imageWWW.isDone)
|
|
{
|
|
}
|
|
|
|
Debug.Log(_fitbitData.RawProfileData["fullName"]);
|
|
|
|
//we should check to see if there is "data" already
|
|
if (_fitbitData.ProfileData.Count != 0)
|
|
{
|
|
foreach (KeyValuePair<string, string> kvp in _fitbitData.ProfileData)
|
|
{
|
|
if (kvp.Key == "avatar")
|
|
continue;
|
|
|
|
//put a space between the camelCase
|
|
var tempKey = Regex.Replace(kvp.Key, "(\\B[A-Z])", " $1");
|
|
//then capitalize the first letter
|
|
UppercaseFirst(tempKey);
|
|
}
|
|
}
|
|
_bGotTheData = true;
|
|
}
|
|
#endregion
|
|
|
|
#region Parsing
|
|
private void ParseAccessToken(JSONObject parsed)
|
|
{
|
|
var dict = parsed.ToDictionary();
|
|
foreach (KeyValuePair<string, string> kvp in dict)
|
|
{
|
|
if (kvp.Key == "access_token")
|
|
{
|
|
_oAuth2.Token = kvp.Value;
|
|
PlayerPrefs.SetString("FitbitAccessToken", kvp.Value);
|
|
}
|
|
else if (kvp.Key == "expires_in")
|
|
{
|
|
var num = 0;
|
|
Int32.TryParse(kvp.Value, out num);
|
|
_oAuth2.ExpiresIn = num;
|
|
|
|
}
|
|
else if (kvp.Key == "refresh_token")
|
|
{
|
|
_oAuth2.RefreshToken = kvp.Value;
|
|
Debug.Log("REFRESH TOKEN: " + kvp.Value);
|
|
PlayerPrefs.SetString("FitbitRefreshToken", kvp.Value);
|
|
Debug.Log("Token We Just Store: " + PlayerPrefs.GetString("FitbitRefreshToken"));
|
|
}
|
|
else if (kvp.Key == "token_type")
|
|
{
|
|
_oAuth2.TokenType = kvp.Value;
|
|
PlayerPrefs.SetString("FitbitTokenType", kvp.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ParseProfileData(string data)
|
|
{
|
|
Debug.Log("inserting json data into fitbitData.RawProfileData");
|
|
//Debug.LogWarning(data);
|
|
XmlDocument xmldoc = JsonConvert.DeserializeXmlNode(data);
|
|
|
|
var doc = XDocument.Parse(xmldoc.InnerXml);
|
|
|
|
|
|
doc.Descendants("topBadges").Remove();
|
|
foreach (XElement xElement in doc.Descendants())
|
|
{
|
|
//Debug.Log(xElement.Name.LocalName + ": Value:" + xElement.Value);V
|
|
if (!_fitbitData.RawProfileData.ContainsKey(xElement.Name.LocalName))
|
|
_fitbitData.RawProfileData.Add(xElement.Name.LocalName, xElement.Value);
|
|
else
|
|
{
|
|
//Debug.LogWarning("Key already found in RawProfileData: " + xElement.Name.LocalName);
|
|
//if the key is already in the dict, we will just update the value for consistency.
|
|
_fitbitData.RawProfileData[xElement.Name.LocalName] = xElement.Value;
|
|
}
|
|
|
|
if (_fitbitData.ProfileData.ContainsKey(xElement.Name.LocalName))
|
|
{
|
|
_fitbitData.ProfileData[xElement.Name.LocalName] = xElement.Value;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ParseStepsData(string data)
|
|
{
|
|
//convert the json to xml cause json blows hard.
|
|
XmlDocument json = JsonConvert.DeserializeXmlNode(data);
|
|
|
|
XDocument doc = XDocument.Parse(json.InnerXml);
|
|
var root = doc.Descendants("value").FirstOrDefault();
|
|
_fitbitData.CurrentSteps = ToInt(root.Value);
|
|
Debug.Log("Steps from Fitbit: " + _fitbitData.CurrentSteps);
|
|
PlayerPrefs.SetInt("fitbitSteps", _fitbitData.CurrentSteps);
|
|
}
|
|
|
|
private void ParseDistanceData(string data)
|
|
{
|
|
XmlDocument json = JsonConvert.DeserializeXmlNode(data);
|
|
|
|
XDocument doc = XDocument.Parse(json.InnerXml);
|
|
var root = doc.Descendants("value").FirstOrDefault().Value;
|
|
//trim the value
|
|
if (root.Length > 4)
|
|
root = root.Substring(0, 4);
|
|
|
|
_fitbitData.CurrentDistance = ToDouble(root);
|
|
|
|
Debug.Log("Distance from Fitbit is:" + _fitbitData.CurrentDistance);
|
|
PlayerPrefs.SetFloat("fitbitDistance", (float)_fitbitData.CurrentDistance);
|
|
}
|
|
|
|
private void ParseCaloriesData(string data)
|
|
{
|
|
XmlDocument json = JsonConvert.DeserializeXmlNode(data);
|
|
|
|
var doc = XDocument.Parse(json.InnerXml);
|
|
var calories = doc.Descendants("value").FirstOrDefault().Value;
|
|
|
|
_fitbitData.CurrentCalories = ToInt(calories);
|
|
PlayerPrefs.SetInt("fitbitCalories", ToInt(calories));
|
|
}
|
|
|
|
private void ParseSleepData(string data)
|
|
{
|
|
Debug.Log(data);
|
|
XmlDocument json = JsonConvert.DeserializeXmlNode(data);
|
|
|
|
var doc = XDocument.Parse(json.InnerXml);
|
|
var sleepTimeTotal = doc.Descendants("value").FirstOrDefault().Value;
|
|
Debug.Log("Minutes asleep for: " + sleepTimeTotal);
|
|
|
|
_fitbitData.CurrentSleep = ToInt(sleepTimeTotal);
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
static string UppercaseFirst(string s)
|
|
{
|
|
// Check for empty string.
|
|
if (string.IsNullOrEmpty(s))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
// Return char and concat substring.
|
|
return char.ToUpper(s[0]) + s.Substring(1);
|
|
}
|
|
|
|
IEnumerator WaitForAccess(WWW www)
|
|
{
|
|
Debug.Log("waiting for access\n");
|
|
yield return www;
|
|
Debug.Log("Past the Yield \n");
|
|
// check for errors
|
|
if (www.error == null)
|
|
{
|
|
Debug.Log("no error \n");
|
|
Debug.Log("Steps from Fitbit: " + _fitbitData.CurrentSteps);
|
|
Debug.Log(www.text);
|
|
Debug.Log("Steps from Fitbit: " + _fitbitData.CurrentSteps);
|
|
}
|
|
if (www.error != null)
|
|
{
|
|
Debug.Log("\n Error" + www.error);
|
|
Debug.Log(www.error);
|
|
}
|
|
Debug.Log("end of WaitForAccess \n");
|
|
}
|
|
|
|
//just a utility function to get the correct date format for activity calls that require one
|
|
public static string GetCurrentDate()
|
|
{
|
|
var date = "";
|
|
date += DateTime.Now.Year;
|
|
if (DateTime.Now.Month < 10)
|
|
{
|
|
date += "-" + "0" + DateTime.Now.Month;
|
|
}
|
|
else
|
|
{
|
|
date += "-" + DateTime.Now.Month;
|
|
}
|
|
|
|
if (DateTime.Now.Day < 10)
|
|
{
|
|
date += "-" + "0" + DateTime.Now.Day;
|
|
}
|
|
else
|
|
{
|
|
date += "-" + DateTime.Now.Day;
|
|
}
|
|
//date += "-" + 15;
|
|
return date;
|
|
}
|
|
|
|
private int ToInt(string thing)
|
|
{
|
|
var temp = 0;
|
|
Int32.TryParse(thing, out temp);
|
|
return temp;
|
|
}
|
|
|
|
private double ToDouble(string thing)
|
|
{
|
|
var temp = 0.0;
|
|
Double.TryParse(thing, out temp);
|
|
return temp;
|
|
}
|
|
|
|
}
|
|
|
|
}
|