Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 8577c93

Browse filesBrowse files
committed
fitness track part 2
1 parent 8d2c145 commit 8577c93
Copy full SHA for 8577c93

File tree

Expand file treeCollapse file tree

20 files changed

+704
-10
lines changed
Filter options
Expand file treeCollapse file tree

20 files changed

+704
-10
lines changed
+125Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import json
2+
from pprint import pprint
3+
4+
def seconds_to_human_friendly_time_string(seconds):
5+
hours = int(seconds / 3600)
6+
minutes = int((seconds % 3600) / 60)
7+
seconds = int(seconds % 60)
8+
# Handle zero cases
9+
if hours == 0 and minutes == 0 and seconds == 0:
10+
return "0 seconds"
11+
# Handle hours
12+
if hours > 0:
13+
return f"{hours} hours and {minutes} minutes and {seconds} seconds"
14+
# Handle minutes
15+
if minutes > 0:
16+
return f"{minutes} minutes and {seconds} seconds"
17+
# Handle seconds
18+
if seconds > 0:
19+
return f"{seconds} seconds"
20+
return "0 seconds"
21+
22+
with open("fitness_plan_.json", "r") as f:
23+
data = json.load(f)
24+
entries = data["exerciseLogEntries"]
25+
entries = [e for e in entries if e['userId'] == '89760760' and e["exerciseName"] != "Rest"]
26+
27+
"""
28+
Entry sample:
29+
{
30+
"tenantId": 1,
31+
"userId": "89760760",
32+
"exerciseId": 1555609163,
33+
"herculesExerciseId": "602621587d678600085608e2",
34+
"executionType": "TIMED",
35+
"meta": {
36+
"weightUnit": "KG",
37+
"durationUnit": "SECOND",
38+
"distanceUnit": "KILOMETRE"
39+
},
40+
"personalBest": {
41+
"id": 791910,
42+
"createdOn": "2023-02-12T08:38:49.000+00:00",
43+
"lastModifiedOn": "2023-02-12T08:38:49.000+00:00",
44+
"createdBy": "system",
45+
"version": 0,
46+
"tenantId": 1,
47+
"userId": "89760760",
48+
"herculesExerciseId": "602621587d678600085608e2",
49+
"userFitnessLevel": "INTERMEDIATE",
50+
"duration": 5400,
51+
"distance": 11.24,
52+
"fromTime": "2023-02-12T08:38:49Z"
53+
},
54+
"previousBest": {
55+
"duration": 1860,
56+
"distance": 4.04
57+
},
58+
"thumbnailUrl": "hercules/production/assets/movements/images/CROSS_TRAINER_v1631535828209_aa25d179-c9fa-4570-acc7-3d7a489b7ee9.jpg",
59+
"exerciseName": "Cross Trainer",
60+
"exerciseType": "DISTANCE",
61+
"lateral": "BILATERAL",
62+
"templateLog": {
63+
"sequence": null,
64+
"unilateralDirection": null,
65+
"weight": null,
66+
"duration": null,
67+
"count": null,
68+
"distance": null,
69+
"value1": "4.04",
70+
"unit1": "kms",
71+
"separator": "x",
72+
"value2": "31",
73+
"unit2": "mins"
74+
},
75+
"fpExecutionLogs": [
76+
{
77+
"sequence": null,
78+
"unilateralDirection": null,
79+
"weight": null,
80+
"duration": 1860,
81+
"count": null,
82+
"distance": 4.04,
83+
"value1": "4.04",
84+
"unit1": "kms",
85+
"separator": "/",
86+
"value2": "31",
87+
"unit2": "mins"
88+
}
89+
]
90+
}
91+
"""
92+
# Create a CSV showing off exercise name, type, lateral, personal_best in the format (count * weight kgs) or (distance (kms), duration (seconds)) whichever fields are non-null in personalBest json dict, time when this personal best was achieved, previous_best in the same format, time when this previous best was achieved, and the thumbnailUrl
93+
import csv
94+
with open("fitness_plan_.csv", "w", newline="") as csvfile:
95+
csv_writer = csv.writer(csvfile, delimiter="\t")
96+
csv_writer.writerow(["ExerciseName", "ExerciseType", "Lateral", "PersonalBest", "PersonalBestTime", "PreviousBest", "ThumbnailUrl"])
97+
for entry in entries:
98+
exerciseName = entry["exerciseName"]
99+
exerciseType = entry["exerciseType"]
100+
lateral = entry["lateral"]
101+
personalBest = entry.get("personalBest", {})
102+
personalBestStr = ""
103+
if len(personalBest) == 0:
104+
personalBestStr = "No personal best"
105+
if "duration" in personalBest:
106+
personalBestStr = f"{seconds_to_human_friendly_time_string(personalBest['duration'])}"
107+
if "distance" in personalBest:
108+
personalBestStr = f"{personalBest['distance']} kms in {personalBestStr}"
109+
elif "weight" in personalBest:
110+
personalBestStr = f"{personalBest['count']} * {personalBest['weight']} kgs"
111+
elif "count" in personalBest:
112+
personalBestStr = f"{personalBest['count']} reps"
113+
personalBestTime = ""
114+
if "fromTime" in personalBest:
115+
personalBestTime = personalBest["fromTime"]
116+
previousBest = entry.get("previousBest", {})
117+
previousBestStr = ""
118+
if "duration" in previousBest:
119+
previousBestStr = f"{seconds_to_human_friendly_time_string(previousBest['duration'])}"
120+
if "distance" in previousBest:
121+
previousBestStr = f"{previousBest['distance']} kms in {previousBestStr}"
122+
elif "weight" in previousBest:
123+
previousBestStr = f"{previousBest['count']} * {previousBest['weight']} kgs"
124+
thumbnailUrl = "https://cdn-images.cure.fit/www-curefit-com/image/upload/" + entry["thumbnailUrl"]
125+
csv_writer.writerow([exerciseName, exerciseType, lateral, personalBestStr, personalBestTime, previousBestStr, thumbnailUrl])
+27Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import json
2+
import requests
3+
4+
# Replace <SECRET> with your actual secret token
5+
headers = {
6+
"accept": "application/json",
7+
# "cookie": "<SECRET>", # get from the cult website, any curl call, get the value of header `Cookie`...
8+
# "at": "<SECRET>", # For API call intercepted via http-toolkit, this is sent as a header in almost all API calls.. I guess one of cookie and at should be passed, I passed at here, and cookie in previous script..
9+
"osname": "android",
10+
"clientversion": "10.08",
11+
"connection": "Keep-Alive",
12+
"content-type": "application/json; charset=utf-8"
13+
}
14+
15+
wodID = ""
16+
url = f"https://www.cult.fit/api/v2/fitnessplanner/exercisesForLogging"
17+
response = requests.post(url, headers=headers, data=json.dumps({
18+
"exerciseIds": [1555609100, 1555609101, 1555609102, 1555609103, 1555609104, 1555609105, 1555609106, 1555609107, 1555609108, 1555609109, 1555609110, 1555609111, 1555609112, 1555609113, 1555609114, 1555609115, 1555609116, 1555609117, 1555609118, 1555609119, 1555609120, 1555609121, 1555609122, 1555609123, 1555609124, 1555609125, 1555609126, 1555609127, 1555609128, 1555609129, 1555609130, 1555609131, 1555609132, 1555609133, 1555609134, 1555609135, 1555609136, 1555609137, 1555609138, 1555609139, 1555609140, 1555609141, 1555609142, 1555609143, 1555609144, 1555609145, 1555609146, 1555609147, 1555609148, 1555609149, 1555609150, 1555609151, 1555609152, 1555609153, 1555609154, 1555609155, 1555609156, 1555609157, 1555609158, 1555609159, 1555609160, 1555609161, 1555609162, 1555609163, 1555609165, 1555609166, 1555609167, 1555609168, 1555609169, 1555609170, 1555609171, 1555609172, 1555609173, 1555609174, 1555609175, 1555609176, 1555609177, 1555609178, 1555609179, 1555609180, 1555609181, 1555609182, 1555609183, 1555609184, 1555609185, 1555609186, 1555609187, 1555609188, 1555609189, 1555609190, 1555609191, 1555609192, 1555609193, 1555609194, 1555609195, 1555609196, 1555609197, 1555609198, 1555609199, 1555609200, 1555609201, 1555609202, 1555609203, 1555609204, 1555609205, 1555609206, 1555609207, 1555609208, 1555609209, 1555609210, 1555609211, 1555609212, 1555609213, 1555609214, 1555609215, 1555609216, 1555609217, 1555609218, 1555609219, 1555609220, 1555609221, 1555609222, 1555609223, 1555609224, 1555609225, 1555609226, 1555609227, 1555609228, 1555609229, 1555609230, 1555609231, 1555609232, 1555609233, 1555609234, 1555609235, 1555609236, 1555609237, 1555609238, 1555609239, 1555609240, 1555609241, 1555609242, 1555609243, 1555609244, 1555609245, 1555609246, 1555609247, 1555609248, 1555609249, 1555609250, 1555609251, 1555609252, 1555609253, 1555609254, 1555609255, 1555609256, 1555609257, 1555609258, 1555609259, 1555609260, 1555609261, 1555609262, 1555609263, 1555609264, 1555609265, 1555609266, 1555609267, 1555609268, 1555609269, 1555609270, 1555609271, 1555609272, 1555609273, 1555609274, 1555609275, 1555609276, 1555609277, 1555609278, 1555609279, 1555609282, 1555609283, 1555609284, 1555609285, 1555609286, 1555609287, 1555609288, 1555609289, 1555609290, 1555609291, 1555609292, 1555609293, 1555609294, 1555609295, 1555609296, 1555609297, 1555609298, 1555609299]
19+
}))
20+
print(response.text)
21+
data = response.json()
22+
23+
from pprint import pprint
24+
pprint(data)
25+
26+
with open(f"fitness_plan_{wodID}.json", "w") as f:
27+
json.dump(data, f, indent=4)
Loading
+89Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
+++
2+
title = 'Fitness Track Part 2'
3+
date = 2023-09-30T16:31:59+05:30
4+
draft = false
5+
+++
6+
7+
## Fitness Track Part 2
8+
9+
This post is continuation of [Fitness Track Part 1](/posts/fitness-track/).
10+
11+
I finally extracted out the existing personal best for exercises as well, and added a new page in the appsheet to display them.
12+
13+
This one was slight tricky to implement because there did not seem to be an API which fetches all exercise details, so on trial and error, when I selected an exercise to log it in the app, it seemed to be calling an API `/api/v2/fitnessplanner/exercisesForLogging` with corresponding exerciseId under `exerciseIds` list parameter. I generated ~2-300 IDs nearby that ID and passed in the API.
14+
15+
Earlier I was trying with ~500 IDs but the API kept crashing, and even with 300 IDs, some of the intermediate IDs required removal (God knows why), and finally I got a JSON which looked like this:
16+
17+
<details>
18+
<summary>JSON</summary>
19+
```json
20+
{
21+
"tenantId": 1,
22+
"userId": "89760760",
23+
"exerciseId": 1555609163,
24+
"herculesExerciseId": "602621587d678600085608e2",
25+
"executionType": "TIMED",
26+
"meta": {
27+
"weightUnit": "KG",
28+
"durationUnit": "SECOND",
29+
"distanceUnit": "KILOMETRE"
30+
},
31+
"personalBest": {
32+
"id": 791910,
33+
"createdOn": "2023-02-12T08:38:49.000+00:00",
34+
"lastModifiedOn": "2023-02-12T08:38:49.000+00:00",
35+
"createdBy": "system",
36+
"version": 0,
37+
"tenantId": 1,
38+
"userId": "89760760",
39+
"herculesExerciseId": "602621587d678600085608e2",
40+
"userFitnessLevel": "INTERMEDIATE",
41+
"duration": 5400,
42+
"distance": 11.24,
43+
"fromTime": "2023-02-12T08:38:49Z"
44+
},
45+
"previousBest": {
46+
"duration": 1860,
47+
"distance": 4.04
48+
},
49+
"thumbnailUrl": "hercules/production/assets/movements/images/CROSS_TRAINER_v1631535828209_aa25d179-c9fa-4570-acc7-3d7a489b7ee9.jpg",
50+
"exerciseName": "Cross Trainer",
51+
"exerciseType": "DISTANCE",
52+
"lateral": "BILATERAL",
53+
"templateLog": {
54+
"sequence": null,
55+
"unilateralDirection": null,
56+
"weight": null,
57+
"duration": null,
58+
"count": null,
59+
"distance": null,
60+
"value1": "4.04",
61+
"unit1": "kms",
62+
"separator": "x",
63+
"value2": "31",
64+
"unit2": "mins"
65+
},
66+
"fpExecutionLogs": [
67+
{
68+
"sequence": null,
69+
"unilateralDirection": null,
70+
"weight": null,
71+
"duration": 1860,
72+
"count": null,
73+
"distance": 4.04,
74+
"value1": "4.04",
75+
"unit1": "kms",
76+
"separator": "/",
77+
"value2": "31",
78+
"unit2": "mins"
79+
}
80+
]
81+
}
82+
```
83+
</details>
84+
85+
Most of the parameters are self explanatory, rest I found by analyzing some of the JSONs manually. The analysis script using which I finally created a CSV is [analysis.py](./analysis.py), and the script to extract those JSONs is [cult_data_exercises.py](./cult_data_exercises.py).
86+
87+
Finally, it looks in my app like this:
88+
89+
![personal bests](images/image.png)

‎content/posts/fitness-track/cult_data_extract.py

Copy file name to clipboardExpand all lines: content/posts/fitness-track/cult_data_extract.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# Replace <SECRET> with your actual secret token
88
headers = {
99
"accept": "application/json",
10-
"cookie": "", # get from the cult website, any curl call, get the value of header `Cookie`...
10+
"cookie": "<SECRET>", # get from the cult website, any curl call, get the value of header `Cookie`...
1111
"clientversion": "10.08",
1212
"connection": "Keep-Alive",
1313
"content-type": "application/json; charset=utf-8"

‎public/categories/index.html

Copy file name to clipboardExpand all lines: public/categories/index.html
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ <h2 class="side-title">
7878

7979
<ul>
8080

81+
<li>
82+
<a href="/posts/fitness-track-part-2/">Fitness Track Part 2</a>
83+
</li>
84+
8185
<li>
8286
<a href="/posts/fitness-track/">Fitness Track</a>
8387
</li>

‎public/index.html

Copy file name to clipboardExpand all lines: public/index.html
+26Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,28 @@
7171

7272

7373

74+
<section class="list-item">
75+
<h1 class="title"><a href="/posts/fitness-track-part-2/">Fitness Track Part 2</a></h1>
76+
77+
<div class="tips">
78+
<div class="date">
79+
<time datetime="2023-09-30 16:31:59 &#43;0530 IST">2023/09/30</time>
80+
</div>
81+
82+
83+
84+
85+
</div>
86+
87+
<div class="summary">
88+
89+
Fitness Track Part 2 🔗This post is continuation of Fitness Track Part 1.
90+
I finally extracted out the existing personal best for exercises as well, and added a new page in the appsheet to display them.
91+
This one was slight tricky to implement because there did not seem to be an API which fetches all exercise details, so on trial and error, when I selected an exercise to log it in the app, it seemed to be calling an API /api/v2/fitnessplanner/exercisesForLogging with corresponding exerciseId under exerciseIds list parameter.
92+
93+
</div>
94+
</section>
95+
7496
<section class="list-item">
7597
<h1 class="title"><a href="/posts/fitness-track/">Fitness Track</a></h1>
7698

@@ -158,6 +180,10 @@ <h2 class="side-title">
158180

159181
<ul>
160182

183+
<li>
184+
<a href="/posts/fitness-track-part-2/">Fitness Track Part 2</a>
185+
</li>
186+
161187
<li>
162188
<a href="/posts/fitness-track/">Fitness Track</a>
163189
</li>

‎public/index.xml

Copy file name to clipboardExpand all lines: public/index.xml
+18-1Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,24 @@
66
<description>Recent content on My Personal Blog</description>
77
<generator>Hugo -- gohugo.io</generator>
88
<language>en-us</language>
9-
<lastBuildDate>Fri, 29 Sep 2023 20:48:38 +0530</lastBuildDate><atom:link href="https://singhcoder.github.io/index.xml" rel="self" type="application/rss+xml" />
9+
<lastBuildDate>Sat, 30 Sep 2023 16:31:59 +0530</lastBuildDate><atom:link href="https://singhcoder.github.io/index.xml" rel="self" type="application/rss+xml" />
10+
<item>
11+
<title>Fitness Track Part 2</title>
12+
<link>https://singhcoder.github.io/posts/fitness-track-part-2/</link>
13+
<pubDate>Sat, 30 Sep 2023 16:31:59 +0530</pubDate>
14+
15+
<guid>https://singhcoder.github.io/posts/fitness-track-part-2/</guid>
16+
<description>&lt;h2 id=&#34;fitness-track-part-2&#34;&gt;Fitness Track Part 2 &lt;a href=&#34;#fitness-track-part-2&#34; class=&#34;anchor&#34;&gt;🔗&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;This post is continuation of &lt;a href=&#34;https://singhcoder.github.io/posts/fitness-track/&#34;&gt;Fitness Track Part 1&lt;/a&gt;.&lt;/p&gt;
17+
&lt;p&gt;I finally extracted out the existing personal best for exercises as well, and added a new page in the appsheet to display them.&lt;/p&gt;
18+
&lt;p&gt;This one was slight tricky to implement because there did not seem to be an API which fetches all exercise details, so on trial and error, when I selected an exercise to log it in the app, it seemed to be calling an API &lt;code&gt;/api/v2/fitnessplanner/exercisesForLogging&lt;/code&gt; with corresponding exerciseId under &lt;code&gt;exerciseIds&lt;/code&gt; list parameter. I generated ~2-300 IDs nearby that ID and passed in the API.&lt;/p&gt;
19+
&lt;p&gt;Earlier I was trying with ~500 IDs but the API kept crashing, and even with 300 IDs, some of the intermediate IDs required removal (God knows why), and finally I got a JSON which looked like this:&lt;/p&gt;
20+
&lt;!-- raw HTML omitted --&gt;
21+
&lt;p&gt;Most of the parameters are self explanatory, rest I found by analyzing some of the JSONs manually. The analysis script using which I finally created a CSV is &lt;a href=&#34;./analysis.py&#34;&gt;analysis.py&lt;/a&gt;, and the script to extract those JSONs is &lt;a href=&#34;./cult_data_exercises.py&#34;&gt;cult_data_exercises.py&lt;/a&gt;.&lt;/p&gt;
22+
&lt;p&gt;Finally, it looks in my app like this:&lt;/p&gt;
23+
&lt;p&gt;&lt;img src=&#34;images/image.png&#34; alt=&#34;personal bests&#34;&gt;&lt;/p&gt;
24+
</description>
25+
</item>
26+
1027
<item>
1128
<title>Fitness Track</title>
1229
<link>https://singhcoder.github.io/posts/fitness-track/</link>

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.