Private
Public Access
1
0

adding intervals.icu webhook support

This commit is contained in:
2024-12-24 09:56:52 +01:00
parent dee230688a
commit 99375e5c9c
3 changed files with 143 additions and 2 deletions

View File

@@ -49,6 +49,8 @@ headers = {
intervals_authorize_url = 'https://intervals.icu/oauth/authorize?' intervals_authorize_url = 'https://intervals.icu/oauth/authorize?'
intervals_token_url = 'https://intervals.icu/api/oauth/token' intervals_token_url = 'https://intervals.icu/api/oauth/token'
webhookverification = 'JA9Vt6RNH10'
class IntervalsIntegration(SyncIntegration): class IntervalsIntegration(SyncIntegration):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(IntervalsIntegration, self).__init__(*args, **kwargs) super(IntervalsIntegration, self).__init__(*args, **kwargs)
@@ -335,6 +337,28 @@ class IntervalsIntegration(SyncIntegration):
data = response.json() data = response.json()
return data return data
def update_plannedsession(self, ps, data, *args, **kwargs):
_ = self.open()
r = self.rower
if data['category'] == 'WORKOUT':
url = self.oauth_data['base_url'] + 'athlete/0/events/' + str(ps.intervals_icu_id) + '/downloadfit'
headers = {
'Authorization': 'Bearer ' + r.intervals_token,
}
response = requests.get(url, headers=headers)
if response.status_code != 200:
dologging('intervals.icu.log', response.text)
else:
filename = 'planned_' + str(ps.intervals_icu_id) + '.fit'
filename2 = 'media/planned_' + str(ps.intervals_icu_id) + '.fit'
with open(filename2, 'wb') as f:
f.write(response.content)
data['fitfile'] = filename
return data
def get_plannedsession(self, id, *args, **kwargs): def get_plannedsession(self, id, *args, **kwargs):
_ = self.open() _ = self.open()
@@ -396,6 +420,7 @@ class IntervalsIntegration(SyncIntegration):
"name": ps.name, "name": ps.name,
"description": stepstext, "description": stepstext,
"indoor": ps.sessionsport in mytypes.ergtypes, "indoor": ps.sessionsport in mytypes.ergtypes,
'external_id': ps.id,
} }
if ps.sessiontype == 'cycletarget': if ps.sessiontype == 'cycletarget':

View File

@@ -633,6 +633,7 @@ urlpatterns = [
name='workout_rojaboimport_view'), name='workout_rojaboimport_view'),
re_path(r'^session/intervalsimport/$', views.plannedsession_intervalsimport_view, re_path(r'^session/intervalsimport/$', views.plannedsession_intervalsimport_view,
name='plannedsession_intervalsimport_view'), name='plannedsession_intervalsimport_view'),
re_path(r'^session/intervals/webhook/$', views.intervals_webhook_view, name='intervals_webhook_view'),
re_path(r'^workout/(?P<source>\w+.*)import/$', re_path(r'^workout/(?P<source>\w+.*)import/$',
views.workout_import_view, name='workout_import_view'), views.workout_import_view, name='workout_import_view'),
re_path(r'^workout/(?P<source>\w+.*)import/(?P<externalid>\d+)/$', re_path(r'^workout/(?P<source>\w+.*)import/(?P<externalid>\d+)/$',

View File

@@ -10,7 +10,9 @@ from rowers.utils import NoTokenError
from rowers.models import PlannedSession from rowers.models import PlannedSession
import rowers.integrations.strava as strava import rowers.integrations.strava as strava
import rowers.integrations.intervals as intervals
from rowers.integrations import importsources from rowers.integrations import importsources
from rowers.integrations import IntervalsIntegration
from rowers.utils import NoTokenError from rowers.utils import NoTokenError
import numpy import numpy
@@ -913,9 +915,122 @@ def workout_rojaboimport_view(request, message="", userid=0): # pragma: no cover
@csrf_exempt
def intervals_webhook_view(request):
if request.method == 'GET':
verificationtoken = request.GET.get('secret')
if verificationtoken != intervals.webhookverification:
return HttpResponse(status=403)
dologging("intervals_webhooks.log","GET request")
dologging("intervals_webhooks.log",request.body)
else:
data = json.loads(request.body)
try:
verificationtoken = data['secret']
except KeyError:
return HttpResponse(status=403)
if verificationtoken != intervals.webhookverification:
return HttpResponse(status=403)
try:
events = data['events']
except KeyError:
# return invalid request if no events
return HttpResponse(status=200)
for event in events:
try:
athlete_id = event['athlete_id']
r = Rower.objects.get(intervals_owner_id=athlete_id)
except Rower.DoesNotExist:
return HttpResponse(status=200)
except MultipleObjectsReturned:
rs = Rower.objects.filter(intervals_owner_id=athlete_id)
r = rs[0]
except KeyError:
return HttpResponse(status=200)
integration = IntervalsIntegration(r.user)
try:
records = event["events"]
except KeyError:
records = []
for record in records:
id = record['id']
data = {}
try:
pss = PlannedSession.objects.filter(intervals_icu_id=id)
if pss.count() > 0:
ps = pss[0]
data = integration.update_plannedsession(ps, record)
else:
data = integration.get_plannedsession(id)
ps = PlannedSession(
manager=r.user,
intervals_icu_id=id,
)
ps.save()
ps.rower.add(r)
except PlannedSession.DoesNotExist:
continue
# got data
if data:
ps.name = data['name']
ps.comment = data['description']
ps.startdate = arrow.get(data['start_date_local']).datetime
ps.enddate = arrow.get(data['end_date_local']).datetime
ps.preferreddate = arrow.get(data['start_date_local']).datetime
ps.sessionsport = mytypes.intervalsmappinginv[data['type']]
ps.sessiontype = 'session'
ps.save()
try:
timetarget = data['time_target']
except KeyError:
timetarget = None
if timetarget is None:
try:
timetarget = data['moving_time']
except KeyError:
timetarget = None
if timetarget is None:
timetarget = 3600
timetarget = int(timetarget)/60.
ps.sessionvalue = timetarget
ps.save()
if data['category'].lower() == 'workout':
ps.fitfile = data['fitfile']
ps.save()
ps.update_steps()
if data['category'].lower() == 'target':
ps.sessiontype = 'cycletarget'
ps.sessionvalue = int(data['time_target'])/60.
ps.enddate = ps.startdate + datetime.timedelta(days=6)
ps.save()
try:
deleted_records = event["deleted_events"]
except KeyError:
deleted_records = []
for record in deleted_records:
id = record['id']
try:
pss = PlannedSession.objects.filter(intervals_icu_id=id)
if r.intervals_delete_plannedsession and pss.count() > 0:
for ps in pss:
ps.delete()
except PlannedSession.DoesNotExist:
continue
return HttpResponse(status=200)
# for Strava webhook request validation # for Strava webhook request validation
@csrf_exempt @csrf_exempt
def strava_webhook_view(request): def strava_webhook_view(request):
if request.method == 'GET': if request.method == 'GET':