Private
Public Access
1
0

import somewhat working (workout type not)

This commit is contained in:
2024-12-08 17:44:45 +01:00
parent 931bab29ea
commit 53e6fefbfe
10 changed files with 202 additions and 14 deletions

View File

@@ -109,7 +109,7 @@ class SyncIntegration(metaclass=ABCMeta):
if 'grant_type' in self.oauth_data: if 'grant_type' in self.oauth_data:
if self.oauth_data['grant_type']: if self.oauth_data['grant_type']:
post_data['grant_type'] = self.oauth_data['grant_type'] post_data['grant_type'] = self.oauth_data['grant_type']
if 'strava' in self.oauth_data['autorization_uri']: if 'strava' in self.oauth_data['authorization_uri']:
post_data['grant_type'] = "authorization_code" post_data['grant_type'] = "authorization_code"
if 'json' in self.oauth_data['content_type']: if 'json' in self.oauth_data['content_type']:

View File

@@ -6,6 +6,7 @@ from rowers import mytypes
from rowers.rower_rules import is_workout_user, ispromember from rowers.rower_rules import is_workout_user, ispromember
from rowers.utils import myqueue, dologging, custom_exception_handler from rowers.utils import myqueue, dologging, custom_exception_handler
from rowers.tasks import handle_intervals_getworkout
import urllib import urllib
import gzip import gzip
@@ -26,13 +27,25 @@ queue = django_rq.get_queue('default', default_timeout=3600)
queuelow = django_rq.get_queue('low', default_timeout=3600) queuelow = django_rq.get_queue('low', default_timeout=3600)
queuehigh = django_rq.get_queue('high', default_timeout=3600) queuehigh = django_rq.get_queue('high', default_timeout=3600)
def seconds_to_duration(seconds):
hours = seconds // 3600
minutes = (seconds % 3600) // 60
remaining_seconds = seconds % 60
# Format as "H:MM:SS" or "MM:SS" if no hours
if hours > 0:
return f"{int(hours)}:{int(minutes):02}:{int(remaining_seconds):02}"
else:
return f"{int(minutes)}:{int(remaining_seconds):02}"
headers = { headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json'
} }
intervals_authorize_url = 'https://intervals.icu/oauth/authorize?' intervals_authorize_url = 'https://intervals.icu/oauth/authorize?'
intervals_token_url = 'https://intervals.icu/oauth/token' intervals_token_url = 'https://intervals.icu/api/oauth/token'
class IntervalsIntegration(SyncIntegration): class IntervalsIntegration(SyncIntegration):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -47,14 +60,33 @@ class IntervalsIntegration(SyncIntegration):
'expirydatename': 'intervals_exp', 'expirydatename': 'intervals_exp',
'refreshtokenname': 'intervals_r', 'refreshtokenname': 'intervals_r',
'bearer_auth': True, 'bearer_auth': True,
'base_uri': 'https://intervals.icu/api/v1/', 'base_url': 'https://intervals.icu/api/v1/',
'grant_type': 'refresh_token', 'grant_type': 'refresh_token',
'headers': headers, 'headers': headers,
'scope': 'ACTIVITY:WRITE' 'scope': 'ACTIVITY:WRITE, LIBRARY:READ',
} }
def get_token(self, code, *args, **kwargs): def get_token(self, code, *args, **kwargs):
return super(IntervalsIntegration, self).get_token(code, *args, **kwargs) post_data = {
'client_id': str(self.oauth_data['client_id']),
'client_secret': self.oauth_data['client_secret'],
'code': code,
}
response = requests.post(
intervals_token_url,
data=post_data,
)
if response.status_code not in [200, 201]:
dologging('intervals.icu.log',response.text)
return [0,"Failed to get token. ",0]
token_json = response.json()
access_token = token_json['access_token']
athlete = token_json['athlete']
return [access_token, athlete, '']
def get_name(self): def get_name(self):
return 'Intervals' return 'Intervals'
@@ -63,8 +95,8 @@ class IntervalsIntegration(SyncIntegration):
return 'intervals' return 'intervals'
def open(self, *args, **kwargs): def open(self, *args, **kwargs):
dologging('intervals.icu.log', "Getting token for user {id}".format(id=self.rower.id)) # dologging('intervals.icu.log', "Getting token for user {id}".format(id=self.rower.id))
token = super(IntervalsIntegration).open(*args, **kwargs) token = super(IntervalsIntegration, self).open(*args, **kwargs)
return token return token
def createworkoutdata(self, w, *args, **kwargs) -> str: def createworkoutdata(self, w, *args, **kwargs) -> str:
@@ -73,13 +105,63 @@ class IntervalsIntegration(SyncIntegration):
def workout_export(self, workout, *args, **kwargs) -> str: def workout_export(self, workout, *args, **kwargs) -> str:
return NotImplemented return NotImplemented
def get_workouts(workout, *args, **kwargs) -> int: def get_workout_list(self, *args, **kwargs) -> int:
return NotImplemented url = self.oauth_data['base_url'] + 'athlete/0/activities?'
startdate = timezone.now() - timedelta(days=365)
enddate = timezone.now() + timedelta(days=1)
url += 'oldest=' + startdate.strftime('%Y-%m-%d') + '&newest=' + enddate.strftime('%Y-%m-%d')
headers = {
'accept': '*/*',
'authorization': 'Bearer ' + self.open(),
}
response = requests.get(url, headers=headers)
if response.status_code != 200:
dologging('intervals.icu.log', response.text)
return []
data = response.json()
known_interval_ids = get_known_ids(self.rower, 'intervalsid')
workouts = []
for item in data:
i = item['id']
r = item['type']
d = item['distance']
ttot = seconds_to_duration(item['moving_time'])
s = item['start_date']
s2 = ''
c = item['name']
if i in known_interval_ids:
nnn = ''
else:
nnn = 'NEW'
keys = ['id','distance','duration','starttime',
'rowtype','source','name','new']
values = [i, d, ttot, s, r, s2, c, nnn]
ress = dict(zip(keys, values))
workouts.append(ress)
return workouts
def get_workout(self, id, *args, **kwargs) -> int: def get_workout(self, id, *args, **kwargs) -> int:
return NotImplemented _ = self.open()
r = self.rower
def get_workout_list(self, *args, **kwargs) -> list: record = create_or_update_syncrecord(r, None, intervalsid=id)
_ = myqueue(queuehigh,
handle_intervals_getworkout,
self.rower,
self.rower.intervals_token,
id)
def get_workouts(workout, *args, **kwargs) -> list:
return NotImplemented return NotImplemented
def make_authorization_url(self, *args, **kwargs): def make_authorization_url(self, *args, **kwargs):

View File

@@ -1242,8 +1242,7 @@ class Rower(models.Model):
intervals_token = models.CharField( intervals_token = models.CharField(
default='', max_length=200, blank=True, null=True) default='', max_length=200, blank=True, null=True)
intervals_exp = models.DateTimeField(blank=True, null=True) intervals_owner_id = models.CharField(default='', max_length=200,blank=True, null=True)
intervals_r = models.CharField(default='', max_length=200, blank=True, null=True)
privacychoices = ( privacychoices = (
('visible', 'Visible'), ('visible', 'Visible'),
@@ -3696,6 +3695,7 @@ class Workout(models.Model):
uploadedtogarmin = models.BigIntegerField(default=0) uploadedtogarmin = models.BigIntegerField(default=0)
uploadedtorp3 = models.BigIntegerField(default=0) uploadedtorp3 = models.BigIntegerField(default=0)
uploadedtonk = models.BigIntegerField(default=0) uploadedtonk = models.BigIntegerField(default=0)
uploadedtointervals = models.BigIntegerField(default=0)
forceunit = models.CharField(default='lbs', forceunit = models.CharField(default='lbs',
choices=( choices=(
('lbs', 'lbs'), ('lbs', 'lbs'),
@@ -3851,6 +3851,7 @@ class SyncRecord(models.Model):
c2id = models.BigIntegerField(unique=True,null=True,default=None) c2id = models.BigIntegerField(unique=True,null=True,default=None)
tpid = models.BigIntegerField(unique=True,null=True,default=None) tpid = models.BigIntegerField(unique=True,null=True,default=None)
rp3id = models.BigIntegerField(unique=True,null=True,default=None) rp3id = models.BigIntegerField(unique=True,null=True,default=None)
intervalsid = models.BigIntegerField(unique=True, null=True, default=None)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.workout: if self.workout:
@@ -3866,7 +3867,7 @@ class SyncRecord(models.Model):
str2 = '' str2 = ''
for field in ['stravaid', 'sporttracksid', 'nkid', 'c2id', 'tpid']: for field in ['stravaid', 'sporttracksid', 'nkid', 'c2id', 'tpid', 'intervalsid']:
value = getattr(self, field, None) value = getattr(self, field, None)
if value is not None: if value is not None:
str2 += '{w}: {v},'.format( str2 += '{w}: {v},'.format(

View File

@@ -24,6 +24,7 @@ from rowers.courseutils import (
InvalidTrajectoryError InvalidTrajectoryError
) )
from rowers.emails import send_template_email from rowers.emails import send_template_email
from rowers.mytypes import fitmappinginv
from rowers.nkimportutils import ( from rowers.nkimportutils import (
get_nk_summary, get_nk_allstats, get_nk_intervalstats, getdict, strokeDataToDf, get_nk_summary, get_nk_allstats, get_nk_intervalstats, getdict, strokeDataToDf,
add_workout_from_data add_workout_from_data
@@ -59,6 +60,8 @@ import rowingdata
from rowingdata import make_cumvalues, make_cumvalues_array from rowingdata import make_cumvalues, make_cumvalues_array
from uuid import uuid4 from uuid import uuid4
from rowingdata import rowingdata as rdata from rowingdata import rowingdata as rdata
from rowingdata import FITParser as FP
from rowingdata.otherparsers import FitSummaryData
from datetime import timedelta from datetime import timedelta
@@ -3485,6 +3488,72 @@ def handle_nk_async_workout(alldata, userid, nktoken, nkid, delaysec, defaulttim
return workoutid return workoutid
@app.task
def handle_intervals_getworkout(rower, intervalstoken, workoutid, debug=False, **kwargs):
authorizationstring = str('Bearer '+intervalstoken)
headers = {
'authorization': authorizationstring,
}
url = "https://intervals.icu/api/v1/activity/{}".format(workoutid)
response = requests.get(url, headers=headers)
if response.status_code != 200:
return 0
data = response.json()
try:
title = data['name']
except KeyError:
title = 'Intervals workout'
try:
workouttype = fitmappinginv[data['type']]
print(data['type'])
except KeyError:
workouttype = 'water'
url = "https://intervals.icu/api/v1/activity/{workoutid}/fit-file".format(workoutid=workoutid)
response = requests.get(url, headers=headers)
if response.status_code != 200:
return 0
try:
fit_data = response.content
fit_filename = 'media/'+f'{uuid4().hex[:16]}.fit'
with open(fit_filename, 'wb') as fit_file:
fit_file.write(fit_data)
except Exception as e:
return 0
try:
row = FP(fit_filename)
rowdata = rowingdata.rowingdata(df=row.df)
rowsummary = FitSummaryData(fit_filename)
duration = totaltime_sec_to_string(rowdata.duration)
distance = rowdata.df[" Horizontal (meters)"].iloc[-1]
except Exception as e:
print(e)
return 0
uploadoptions = {
'secret': UPLOAD_SERVICE_SECRET,
'user': rower.user.id,
'boattype': '1x',
'workouttype': workouttype,
'file': fit_filename,
'title': title,
'rpe': 0,
'notes': '',
'offline': False,
}
url = UPLOAD_SERVICE_URL
handle_request_post(url, uploadoptions)
return 1
@app.task @app.task
def handle_c2_getworkout(userid, c2token, c2id, defaulttimezone, debug=False, **kwargs): def handle_c2_getworkout(userid, c2token, c2id, defaulttimezone, debug=False, **kwargs):

View File

@@ -57,6 +57,7 @@
<li id="sporttracks"><a href="/rowers/workout/sporttracksimport/">SportTracks</a></li> <li id="sporttracks"><a href="/rowers/workout/sporttracksimport/">SportTracks</a></li>
<li id="polar"><a href="/rowers/workout/polarimport/">Polar</a></li> <li id="polar"><a href="/rowers/workout/polarimport/">Polar</a></li>
<li id="rp3"><a href="/rowers/workout/rp3import/">RP3</a></li> <li id="rp3"><a href="/rowers/workout/rp3import/">RP3</a></li>
<li id="intervals"><a href="/rowers/workout/intervalsimport/">Intervals.icu</a></li>
</ul> </ul>
</li> </li>
</ul> <!-- cd-accordion-menu --> </ul> <!-- cd-accordion-menu -->

View File

@@ -123,6 +123,8 @@
alt="connect with RP3" width="130"></a></p> alt="connect with RP3" width="130"></a></p>
<p><a href="/rowers/me/rojaboauthorize"><img src="/static/img/rojabo.png" <p><a href="/rowers/me/rojaboauthorize"><img src="/static/img/rojabo.png"
alt="connect with Rojabo" width="130"></a></p> alt="connect with Rojabo" width="130"></a></p>
<p><a href="/rowers/me/intervalsauthorize"><img src="/static/img/intervals_icu.png"
alt="connect with intervals.icu" height="30"></a></p>
{% endblock %} {% endblock %}

View File

@@ -24,6 +24,7 @@ importauthorizeviews = {
'nk': 'rower_integration_authorize', 'nk': 'rower_integration_authorize',
'rp3': 'rower_integration_authorize', 'rp3': 'rower_integration_authorize',
'garmin': 'rower_garmin_authorize', 'garmin': 'rower_garmin_authorize',
'intervals': 'rower_integration_authorize',
} }
@@ -173,6 +174,37 @@ def rower_process_twittercallback(request): # pragma: no cover
# Process Polar Callback # Process Polar Callback
@login_required()
def rower_process_intervalscallback(request):
integration = importsources['intervals'](request.user)
r = getrower(request.user)
try:
code = request.GET['code']
res = integration.get_token(code)
except MultiValueDictKeyError:
message = "The resource owner or authorization server denied the request"
messages.error(request, message)
url = reverse('rower_exportsettings_view')
return HttpResponseRedirect(url)
access_token = res[0]
athlete = res[1]
if access_token == 0:
message = res[1]
message += 'Connection to intervals.icu failed.'
messages.error(request, message)
url = reverse('rower_exportsettings_view')
return HttpResponseRedirect(url)
r.intervals_token = access_token
r.intervals_owner_id = athlete['id']
r.save()
successmessage = "Tokens stored. Good to go. Please check your import/export settings"
messages.info(request, successmessage)
url = reverse('rower_exportsettings_view')
return HttpResponseRedirect(url)
@login_required() @login_required()
def rower_process_polarcallback(request): def rower_process_polarcallback(request):

View File

@@ -94,6 +94,7 @@ urlpatterns += [
re_path(r'^rp3\_callback', rowersviews.rower_process_rp3callback), re_path(r'^rp3\_callback', rowersviews.rower_process_rp3callback),
re_path(r'^twitter\_callback', rowersviews.rower_process_twittercallback), re_path(r'^twitter\_callback', rowersviews.rower_process_twittercallback),
re_path(r'^idoklad\_callback', rowersviews.process_idokladcallback), re_path(r'^idoklad\_callback', rowersviews.process_idokladcallback),
re_path(r'^intervals\_icu\_callback', rowersviews.rower_process_intervalscallback),
re_path(r'^i18n/', include('django.conf.urls.i18n')), re_path(r'^i18n/', include('django.conf.urls.i18n')),
re_path(r'^tz_detect/', include('tz_detect.urls')), re_path(r'^tz_detect/', include('tz_detect.urls')),
re_path(r'^logo/', logoview), re_path(r'^logo/', logoview),

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB