Skip to content

Commit

Permalink
feat: support tcx
Browse files Browse the repository at this point in the history
  • Loading branch information
yihong0618 committed Jul 6, 2022
1 parent ace5de4 commit 87ef2c8
Show file tree
Hide file tree
Showing 25 changed files with 470 additions and 275 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/run_data_sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: 3.6
python-version: 3.7

# from pdm
- name: Set Variables
Expand Down Expand Up @@ -77,6 +77,7 @@ jobs:
run: |
python scripts/strava_sync.py ${{ secrets.STRAVA_CLIENT_ID }} ${{ secrets.STRAVA_CLIENT_SECRET }} ${{ secrets.STRAVA_CLIENT_REFRESH_TOKEN }}
# for garmin if you want generate `tcx` you can add --tcx command in the args.
- name: Run sync Garmin script
if: env.RUN_TYPE == 'garmin'
run: |
Expand Down Expand Up @@ -116,6 +117,6 @@ jobs:
run: |
git config --local user.email "${{ env.GITHUB_EMAIL }}"
git config --local user.name "${{ env.GITHUB_NAME }}"
git commit -a -m 'update new runs' || echo "nothing to commit"
git add .
git commit -m 'update new runs' || echo "nothing to commit"
git push || echo "nothing to push"
44 changes: 43 additions & 1 deletion README-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ R.I.P. 希望大家都能健康顺利的跑过终点,逝者安息。
- **[悦跑圈](#joyrun 悦跑圈)** (因悦跑圈限制单个设备原因,无法自动化)
- **[咕咚](#codoon 咕咚)** (因咕咚限制单个设备原因,无法自动化)
- **[GPX](#GPX)**
- **[TCX](#TCX)**
- **[Tcx_to_Strava(upload all tcx data to strava)](#TCX_to_Strava)**
- **[Nike+Strava(Using NRC Run, Strava backup data)](#nikestrava)**
- **[Strava_to_Garmin(Using Strava Run, Garmin backup data)](#)**

Expand All @@ -97,7 +99,7 @@ R.I.P. 希望大家都能健康顺利的跑过终点,逝者安息。
git clone https://github.com/yihong0618/running_page.git --depth=1
```

## 安装及测试 (node >= 12 and <= 14 python >= 3.6)
## 安装及测试 (node >= 12 and <= 14 python >= 3.7)

```
pip3 install -r requirements.txt
Expand Down Expand Up @@ -189,6 +191,20 @@ python3(python) scripts/gpx_sync.py

</details>

### TCX

<details>
<summary>Make your <code>TCX</code> data</summary>
<br>

把其它软件生成的 tcx files 拷贝到 TCX_OUT 之后运行

```python
python3(python) scripts/tcx_sync.py
```

</details>

### Keep

<details>
Expand Down Expand Up @@ -311,6 +327,10 @@ python3(python) scripts/codoon_sync.py 54bxxxxxxx fefxxxxx-xxxx-xxxx --from-auth

<details>
<summary>获取您的 Garmin 数据</summary>
<br>
如果你只想同步跑步数据增加命令 --only-run
如果你想同步 `tcx` 格式,增加命令 --tcx


```python
python3(python) scripts/garmin_sync.py ${your email} ${your password}
Expand Down Expand Up @@ -449,6 +469,28 @@ python3(python) scripts/strava_sync.py ${client_id} ${client_secret} ${refresch_

</details>

### TCX_to_Strava

<details>
<summary>Upload all tcx files to strava</summary>

<br>

1. 完成 strava 的步骤
2. 在项目根目录下执行:

```python
python3(python) scripts/tcx_to_strava_sync.py ${client_id} ${client_secret} ${strava_refresch_token}
```

示例:

```python
python3(python) scripts/tcx_to_strava_sync.py xxx xxx xxx
```

</details>

### Nike+Strava

<details>
Expand Down
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ English | [简体中文](https://github.com/yihong0618/running_page/blob/master/
- **[Nike Run Club](#nike-run-club)**
- **[Strava](#strava)**
- **[GPX](#GPX)**
- **[TCX](#TCX)**
- **[Nike_to_Strava(Using NRC Run, Strava backup data)](#Nike_to_Strava)**
- **[Tcx_to_Strava(upload all tcx data to strava)](#TCX_to_Strava)**
- **[Strava_to_Garmin(Using Strava Run, Garmin backup data)](#)**

## Download
Expand All @@ -88,7 +90,7 @@ Clone or fork the repo.
git clone https://github.com/yihong0618/running_page.git --depth=1
```

## Installation and testing (node >= 12 and <= 14 python >= 3.6)
## Installation and testing (node >= 12 and <= 14 python >= 3.7)

```
pip3 install -r requirements.txt
Expand Down Expand Up @@ -180,12 +182,27 @@ python3(python) scripts/gpx_sync.py

</details>

### TCX

<details>
<summary>Make your <code>TCX</code> data</summary>
<br>

Copy all your tcx files to TCX_OUT or new tcx files

```python
python3(python) scripts/tcx_sync.py
```

</details>

### Garmin

<details>
<summary>Get your <code>Garmin</code> data</summary>
<br>
If you only want to sync `type running` add args --only-run
If you only want `tcx` files add args --tcx

```python
python3(python) scripts/garmin_sync.py ${your email} ${your password}
Expand All @@ -210,6 +227,8 @@ python3(python) scripts/garmin_sync.py [email protected] example --only-run
<details>
<summary>Get your <code>Garmin-CN</code> data</summary>
<br>
If you only want to sync `type running` add args --only-run
If you only want `tcx` files add args --tcx

```python
python3(python) scripts/garmin_sync.py ${your email} ${your password} --is-cn
Expand Down Expand Up @@ -340,6 +359,29 @@ References:

</details>


### TCX_to_Strava

<details>
<summary>upload all tcx files to strava</summary>

<br>

1. follow the strava steps
2. Execute in the root directory:

```python
python3(python) scripts/tcx_to_strava_sync.py ${client_id} ${client_secret} ${strava_refresch_token}
```

example:

```python
python3(python) scripts/tcx_to_strava_sync.py xxx xxx xxx
```

</details>

### Nike_to_Strava

<details>
Expand Down
Empty file added TCX_OUT/.gitkeep
Empty file.
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ timezonefinder
pyyaml
aiofiles
cloudscraper==1.2.58
python-tcxparser
rich
3 changes: 0 additions & 3 deletions scripts/codoon_sync.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import argparse
import base64
import hashlib
Expand Down
1 change: 1 addition & 0 deletions scripts/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
GET_DIR = "activities"
OUTPUT_DIR = "activities"
GPX_FOLDER = os.path.join(os.getcwd(), "GPX_OUT")
TCX_FOLDER = os.path.join(os.getcwd(), "TCX_OUT")
SQL_FILE = os.path.join(os.getcwd(), "scripts", "data.db")
JSON_FILE = os.path.join(os.getcwd(), "src", "static", "activities.json")

Expand Down
9 changes: 4 additions & 5 deletions scripts/endomondo_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@
need to download the files from endomondo
and store it in Workous dir in running_page
"""
import os
import json
import os
from collections import namedtuple
from datetime import datetime, timedelta

from utils import adjust_time
from config import BASE_TIMEZONE, ENDOMONDO_FILE_DIR, SQL_FILE, JSON_FILE
from generator import Generator

import polyline
from config import BASE_TIMEZONE, ENDOMONDO_FILE_DIR, JSON_FILE, SQL_FILE
from generator import Generator

from utils import adjust_time

# TODO Same as keep_sync maybe refactor
start_point = namedtuple("start_point", "lat lon")
Expand Down
53 changes: 35 additions & 18 deletions scripts/garmin_sync.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Python 3 API wrapper for Garmin Connect to get your statistics.
Copy most code from https://github.com/cyberjunky/python-garminconnect
"""

import argparse
import asyncio
import json
import logging
import os
import re
import json
import sys
import time
import traceback

import aiofiles
import cloudscraper
import httpx
from config import GPX_FOLDER, JSON_FILE, SQL_FILE, config
from config import GPX_FOLDER, JSON_FILE, SQL_FILE, TCX_FOLDER, config

from utils import make_activities_file

# logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

FOLDER_DICT = {
"gpx": GPX_FOLDER,
"tcx": TCX_FOLDER,
}

TIME_OUT = httpx.Timeout(240.0, connect=360.0)
GARMIN_COM_URL_DICT = {
Expand Down Expand Up @@ -178,8 +180,8 @@ async def get_activities(self, start, limit):
url = url + "&activityType=running"
return await self.fetch_data(url)

async def download_activity(self, activity_id):
url = f"{self.modern_url}/proxy/download-service/export/gpx/activity/{activity_id}"
async def download_activity(self, activity_id, file_type="gpx"):
url = f"{self.modern_url}/proxy/download-service/export/{file_type}/activity/{activity_id}"
logger.info(f"Download activity from {url}")
response = await self.req.get(url, headers=self.headers)
response.raise_for_status()
Expand All @@ -190,7 +192,6 @@ async def upload_activities(self, files):
self.login()
for file, garmin_type in files:
files = {"data": ("file.gpx", file)}

try:
res = await self.req.post(
self.upload_url, files=files, headers={"nk": "NT"}
Expand Down Expand Up @@ -252,16 +253,16 @@ def __init__(self, status):
self.status = status


async def download_garmin_gpx(client, activity_id):
async def download_garmin_data(client, activity_id, file_type="gpx"):
folder = FOLDER_DICT.get(file_type, "gpx")
try:
gpx_data = await client.download_activity(activity_id)
file_path = os.path.join(GPX_FOLDER, f"{activity_id}.gpx")
file_data = await client.download_activity(activity_id, file_type=file_type)
file_path = os.path.join(folder, f"{activity_id}.{file_type}")
async with aiofiles.open(file_path, "wb") as fb:
await fb.write(gpx_data)
await fb.write(file_data)
except:
print(f"Failed to download activity {activity_id}: ")
traceback.print_exc()
pass


async def get_activity_id_list(client, start=0):
Expand Down Expand Up @@ -300,6 +301,14 @@ async def sem_task(task):
action="store_true",
help="if is only for running",
)
parser.add_argument(
"--tcx",
dest="download_file_type",
action="store_const",
const="tcx",
default="gpx",
help="to download personal documents or ebook",
)
options = parser.parse_args()
email = options.email or config("sync", "garmin", "email")
password = options.password or config("sync", "garmin", "password")
Expand All @@ -310,10 +319,10 @@ async def sem_task(task):
if email == None or password == None:
print("Missing argument nor valid configuration file")
sys.exit(1)

folder = FOLDER_DICT.get(options.download_file_type, "gpx")
# make gpx dir
if not os.path.exists(GPX_FOLDER):
os.mkdir(GPX_FOLDER)
if not os.path.exists(folder):
os.mkdir(folder)

async def download_new_activities():
client = Garmin(email, password, auth_domain, is_only_running)
Expand All @@ -322,18 +331,26 @@ async def download_new_activities():
# because I don't find a para for after time, so I use garmin-id as filename
# to find new run to generage
downloaded_ids = [
i.split(".")[0] for i in os.listdir(GPX_FOLDER) if not i.startswith(".")
i.split(".")[0] for i in os.listdir(folder) if not i.startswith(".")
]
activity_ids = await get_activity_id_list(client)
to_generate_garmin_ids = list(set(activity_ids) - set(downloaded_ids))
print(f"{len(to_generate_garmin_ids)} new activities to be downloaded")

start_time = time.time()
file_type = options.download_file_type
await gather_with_concurrency(
10, [download_garmin_gpx(client, id) for id in to_generate_garmin_ids]
10,
[
download_garmin_data(client, id, file_type=file_type)
for id in to_generate_garmin_ids
],
)
print(f"Download finished. Elapsed {time.time()-start_time} seconds")
make_activities_file(SQL_FILE, GPX_FOLDER, JSON_FILE)

make_activities_file(
SQL_FILE, folder, JSON_FILE, file_suffix=options.download_file_type
)
await client.req.aclose()

loop = asyncio.get_event_loop()
Expand Down
Loading

0 comments on commit 87ef2c8

Please sign in to comment.