def UploadActivity(self, serviceRecord, activity): #/proxy/upload-service-1.1/json/upload/.fit fit_file = FITIO.Dump(activity) files = {"data": ("tap-sync-" + str(os.getpid()) + "-" + activity.UID + ".fit", fit_file)} res = self._request_with_reauth( lambda session: session.post("https://connect.garmin.com/modern/proxy/upload-service/upload/.fit", files=files, headers={"nk": "NT"}), serviceRecord) try: res = res.json()["detailedImportResult"] except ValueError: raise APIException("Bad response during GC upload: %s %s" % (res.status_code, res.text)) if len(res["successes"]) == 0: if len(res["failures"]) and len(res["failures"][0]["messages"]): if res["failures"][0]["messages"][0]["content"] == "Duplicate activity": logger.debug("Duplicate") return # ...cool? if res["failures"][0]["messages"][0]["content"] == "The user is from EU location, but upload consent is not yet granted or revoked": raise APIException("EU user with no upload consent", block=True, user_exception=UserException(UserExceptionType.GCUploadConsent, intervention_required=True)) raise APIException("Unable to upload activity %s" % res) if len(res["successes"]) > 1: raise APIException("Uploaded succeeded, resulting in too many activities") actid = res["successes"][0]["internalId"] name = activity.Name # Capture in logs notes = activity.Notes # Update activity metadata not included in the FIT file. metadata_object = {} if activity.Name and activity.Name.strip(): metadata_object["activityName"] = activity.Name if activity.Notes and activity.Notes.strip(): metadata_object["description"] = activity.Notes if activity.Type not in [ActivityType.Running, ActivityType.Cycling, ActivityType.Other]: # Set the legit activity type - whatever it is, it's not supported by the FIT schema acttype = [k for k, v in self._reverseActivityMappings.items() if v == activity.Type] if len(acttype) == 0: raise APIWarning("GarminConnect does not support activity type " + activity.Type) else: acttype = acttype[0] metadata_object["activityTypeDTO"] = {"typeKey": acttype} if activity.Private: metadata_object["accessControlRuleDTO"] = {"typeKey": "private"} if metadata_object: metadata_object["activityId"] = actid encoding_headers = {"Content-Type": "application/json; charset=UTF-8"} # GC really, really needs this part, otherwise it throws obscure errors like "Invalid signature for signature method HMAC-SHA1" res = self._request_with_reauth(lambda session: session.put("https://connect.garmin.com/proxy/activity-service/activity/" + str(actid), data=json.dumps(metadata_object), headers=encoding_headers), serviceRecord) if res.status_code != 204: raise APIWarning("Unable to set activity metadata - %d %s" % (res.status_code, res.text)) return actid
def UploadActivity(self, serviceRecord, activity): #/proxy/upload-service-1.1/json/upload/.tcx activity.EnsureTZ() tcx_file = TCXIO.Dump(activity) files = {"data": ("tap-sync-" + str(os.getpid()) + "-" + activity.UID + ".tcx", tcx_file)} cookies = self._get_cookies(record=serviceRecord) res = requests.post("http://connect.garmin.com/proxy/upload-service-1.1/json/upload/.tcx", files=files, cookies=cookies) res = res.json()["detailedImportResult"] if len(res["successes"]) != 1: raise APIException("Unable to upload activity") actid = res["successes"][0]["internalId"] if activity.Type not in [ActivityType.Running, ActivityType.Cycling, ActivityType.Other]: # Set the legit activity type - whatever it is, it's not supported by the TCX schema acttype = [k for k, v in self._reverseActivityMappings.items() if v == activity.Type] if len(acttype) == 0: raise APIWarning("GarminConnect does not support activity type " + activity.Type) else: acttype = acttype[0] res = requests.post("http://connect.garmin.com/proxy/activity-service-1.2/json/type/" + str(actid), data={"value": acttype}, cookies=cookies) res = res.json() if "activityType" not in res or res["activityType"]["key"] != acttype: raise APIWarning("Unable to set activity type")
def UploadActivity(self, serviceRecord, activity): #/proxy/upload-service-1.1/json/upload/.fit fit_file = FITIO.Dump(activity) files = { "data": ("tap-sync-" + str(os.getpid()) + "-" + activity.UID + ".fit", fit_file) } session = self._get_session(record=serviceRecord) self._rate_limit() res = session.post( "http://connect.garmin.com/proxy/upload-service-1.1/json/upload/.fit", files=files) res = res.json()["detailedImportResult"] if len(res["successes"]) == 0: raise APIException("Unable to upload activity %s" % res) if len(res["successes"]) > 1: raise APIException( "Uploaded succeeded, resulting in too many activities") actid = res["successes"][0]["internalId"] name = activity.Name # Capture in logs notes = activity.Notes encoding_headers = { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" } # GC really, really needs this part, otherwise it throws obscure errors like "Invalid signature for signature method HMAC-SHA1" warnings = [] try: if activity.Name and activity.Name.strip(): self._rate_limit() res = session.post( "http://connect.garmin.com/proxy/activity-service-1.2/json/name/" + str(actid), data=urlencode({ "value": activity.Name }).encode("UTF-8"), headers=encoding_headers) try: res = res.json() except: raise APIWarning("Activity name request failed - %s" % res.text) if "display" not in res or res["display"][ "value"] != activity.Name: raise APIWarning("Unable to set activity name") except APIWarning as e: warnings.append(e) try: if activity.Notes and activity.Notes.strip(): self._rate_limit() res = session.post( "http://connect.garmin.com/proxy/activity-service-1.2/json/description/" + str(actid), data=urlencode({ "value": activity.Notes }).encode("UTF-8"), headers=encoding_headers) try: res = res.json() except: raise APIWarning("Activity notes request failed - %s" % res.text) if "display" not in res or res["display"][ "value"] != activity.Notes: raise APIWarning("Unable to set activity notes") except APIWarning as e: warnings.append(e) try: if activity.Type not in [ ActivityType.Running, ActivityType.Cycling, ActivityType.Other ]: # Set the legit activity type - whatever it is, it's not supported by the TCX schema acttype = [ k for k, v in self._reverseActivityMappings.items() if v == activity.Type ] if len(acttype) == 0: raise APIWarning( "GarminConnect does not support activity type " + activity.Type) else: acttype = acttype[0] self._rate_limit() res = session.post( "http://connect.garmin.com/proxy/activity-service-1.2/json/type/" + str(actid), data={"value": acttype}) res = res.json() if "activityType" not in res or res["activityType"][ "key"] != acttype: raise APIWarning("Unable to set activity type") except APIWarning as e: warnings.append(e) try: if activity.Private: self._rate_limit() res = session.post( "http://connect.garmin.com/proxy/activity-service-1.2/json/privacy/" + str(actid), data={"value": "private"}) res = res.json() if "definition" not in res or res["definition"][ "key"] != "private": raise APIWarning("Unable to set activity privacy") except APIWarning as e: warnings.append(e) if len(warnings): raise APIWarning(str(warnings)) # Meh return actid