Skip to content

Commit

Permalink
Merge branch 'refs/heads/upstream'
Browse files Browse the repository at this point in the history
# Conflicts:
#	.github/workflows/run_data_sync.yml
#	README-CN.md
#	README.md
#	assets/github.svg
#	assets/grid.svg
#	run_page/generator/__init__.py
#	run_page/gpxtrackposter/track.py
#	run_page/joyrun_sync.py
#	src/static/activities.json
  • Loading branch information
ben-29 committed Aug 19, 2024
2 parents 07fbe13 + 6240702 commit 9d587ff
Show file tree
Hide file tree
Showing 13 changed files with 980 additions and 47 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ jobs:
with:
node-version: '20'

- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
Expand All @@ -100,10 +100,10 @@ jobs:
run: PATH_PREFIX=/${{ github.event.repository.name }} pnpm build

- name: Upload artifact
uses: actions/upload-pages-artifact@v2
uses: actions/upload-pages-artifact@v3
with:
# Upload dist repository
path: './dist'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v3
uses: actions/deploy-pages@v4
17 changes: 16 additions & 1 deletion .github/workflows/run_data_sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ on:
- run_page/keep_sync.py
- run_page/gpx_sync.py
- run_page/tcx_sync.py
- run_page/tcx_to_garmin_sync.py
- run_page/garmin_to_strava_sync.py
- run_page/keep_to_strava_sync.py
- run_page/oppo_sync.py
- requirements.txt

env:
# please change to your own config.
RUN_TYPE: pass # support strava/nike/garmin/coros/garmin_cn/garmin_sync_cn_global/keep/only_gpx/only_fit/nike_to_strava/strava_to_garmin/strava_to_garmin_cn/garmin_to_strava/garmin_to_strava_cn/codoon, Please change the 'pass' it to your own
RUN_TYPE: pass # support strava/nike/garmin/coros/garmin_cn/garmin_sync_cn_global/keep/only_gpx/only_fit/nike_to_strava/strava_to_garmin/tcx_to_garmin/strava_to_garmin_cn/garmin_to_strava/garmin_to_strava_cn/codoon/oppo, Please change the 'pass' it to your own
ATHLETE: ben_29
TITLE: Workouts
MIN_GRID_DISTANCE: 10 # change min distance here
Expand Down Expand Up @@ -121,6 +123,12 @@ jobs:
run: |
python run_page/codoon_sync.py ${{ secrets.CODOON_MOBILE }} ${{ secrets.CODOON_PASSWORD }}
- name: Run sync tcx to Garmin script
if: env.RUN_TYPE == 'tcx_to_garmin'
run: |
# python run_page/tcx_to_garmin_sync.py ${{ secrets.GARMIN_SECRET_STRING }}
python run_page/tcx_to_garmin_sync.py ${{ secrets.GARMIN_SECRET_STRING_CN }} --is-cn
# 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'
Expand Down Expand Up @@ -187,6 +195,13 @@ jobs:
run: |
python run_page/tulipsport_sync.py ${{ secrets.TULIPSPORT_TOKEN }} --with-gpx
- name: Run sync Oppo heytap script, note currently this script is not worked
if: env.RUN_TYPE == 'oppo'
run: |
python run_page/oppo_sync.py ${{ secrets.OPPO_ID }} ${{ secrets.OPPO_CLIENT_SECRET }} ${{ secrets.OPPO_CLIENT_REFRESH_TOKEN }} --with-tcx
# If you want to sync fit activity in gpx format, please consider the following script:
# python run_page/oppo_sync.py ${{ secrets.OPPO_ID }} ${{ secrets.OPPO_CLIENT_SECRET }} ${{ secrets.OPPO_CLIENT_REFRESH_TOKEN }} --with-gpx

- name: Make svg GitHub profile
if: env.RUN_TYPE != 'pass'
run: |
Expand Down
2 changes: 1 addition & 1 deletion assets/github_2024.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion assets/year_2024.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 29 additions & 9 deletions run_page/codoon_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import xml.etree.ElementTree as ET
from collections import namedtuple
from datetime import datetime, timedelta
from xml.dom import minidom

import eviltransform
import gpxpy
Expand Down Expand Up @@ -45,6 +46,8 @@
# device info
user_agent = "CodoonSport(8.9.0 1170;Android 7;Sony XZ1)"
did = "24-00000000-03e1-7dd7-0033-c5870033c588"
# May be Forerunner 945?
CONNECT_API_PART_NUMBER = "006-D2449-00"

# fixed params
base_url = "https://api.codoon.com"
Expand All @@ -61,9 +64,9 @@

# for tcx type
TCX_TYPE_DICT = {
0: "Hike",
0: "Hiking",
1: "Running",
2: "Ride",
2: "Biking",
}

# only for running sports, if you want others, please change the True to False
Expand Down Expand Up @@ -127,6 +130,9 @@ def formated_input(


def tcx_output(fit_array, run_data):
"""
If you want to make a more detailed tcx file, please refer to oppo_sync.py
"""
# route ID
fit_id = str(run_data["id"])
# local time
Expand All @@ -149,7 +155,7 @@ def tcx_output(fit_array, run_data):
},
)
# xml tree
tree = ET.ElementTree(training_center_database)
ET.ElementTree(training_center_database)
# Activities
activities = ET.Element("Activities")
training_center_database.append(activities)
Expand All @@ -163,12 +169,15 @@ def tcx_output(fit_array, run_data):
activity_id.text = fit_start_time # Codoon use start_time as ID
activity.append(activity_id)
# Creator
activity_creator = ET.Element("Creator")
activity_creator = ET.Element("Creator", {"xsi:type": "Device_t"})
activity.append(activity_creator)
# Name
activity_creator_name = ET.Element("Name")
activity_creator_name.text = "咕咚"
activity_creator_name.text = "Codoon"
activity_creator.append(activity_creator_name)
activity_creator_product = ET.Element("ProductID")
activity_creator_product.text = "3441"
activity_creator.append(activity_creator_product)
# Lap
activity_lap = ET.Element("Lap", {"StartTime": fit_start_time})
activity.append(activity_lap)
Expand Down Expand Up @@ -215,11 +224,22 @@ def tcx_output(fit_array, run_data):
altitude_meters = ET.Element("AltitudeMeters")
altitude_meters.text = bytes.decode(i["elevation"])
tp.append(altitude_meters)

# Author
author = ET.Element("Author", {"xsi:type": "Application_t"})
training_center_database.append(author)
author_name = ET.Element("Name")
author_name.text = "Connect Api"
author.append(author_name)
author_lang = ET.Element("LangID")
author_lang.text = "en"
author.append(author_lang)
author_part = ET.Element("PartNumber")
author_part.text = CONNECT_API_PART_NUMBER
author.append(author_part)
# write to TCX file
tree.write(
TCX_FOLDER + "/" + fit_id + ".tcx", encoding="utf-8", xml_declaration=True
)
xml_str = minidom.parseString(ET.tostring(training_center_database)).toprettyxml()
with open(TCX_FOLDER + "/" + fit_id + ".tcx", "w") as f:
f.write(str(xml_str))


# TODO time complexity is too heigh, need to be reduced
Expand Down
2 changes: 1 addition & 1 deletion run_page/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@


BASE_TIMEZONE = "Asia/Shanghai"

UTC_TIMEZONE = "UTC"

start_point = namedtuple("start_point", "lat lon")
run_map = namedtuple("polyline", "summary_polyline")
Expand Down
18 changes: 13 additions & 5 deletions run_page/generator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,8 @@ def sync_from_data_dir(self, data_dir, file_suffix="gpx"):
return

synced_files = []
if file_suffix == "fit":
name_mapping = load_fit_name_mapping()

for t in tracks:
activity_id = t.file_names[0].split(".")[0]
if file_suffix == "fit" and activity_id in name_mapping:
t.name = name_mapping[activity_id]
created = update_or_create_activity(self.session, t.to_namedtuple())
if created:
sys.stdout.write("+")
Expand Down Expand Up @@ -204,3 +199,16 @@ def get_old_tracks_ids(self):
# pass the error
print(f"something wrong with {str(e)}")
return []

def get_old_tracks_dates(self):
try:
activities = (
self.session.query(Activity)
.order_by(Activity.start_date_local.desc())
.all()
)
return [str(a.start_date_local) for a in activities]
except Exception as e:
# pass the error
print(f"something wrong with {str(e)}")
return []
6 changes: 1 addition & 5 deletions run_page/gpxtrackposter/track.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def load_gpx(self, file_name):
# (for example, treadmill runs pulled via garmin-connect-export)
if os.path.getsize(file_name) == 0:
raise TrackLoadError("Empty GPX file")
with open(file_name, "rb") as file:
with open(file_name, "r", encoding="utf-8", errors="ignore") as file:
self._load_gpx_data(mod_gpxpy.parse(file))
except Exception as e:
print(
Expand Down Expand Up @@ -291,10 +291,6 @@ def _load_fit_data(self, fit: dict):
lng = record["position_long"] / SEMICIRCLE
_polylines.append(s2.LatLng.from_degrees(lat, lng))
self.polyline_container.append([lat, lng])
for record in fit["device_info_mesgs"]:
if "device_index" in record and record["device_index"] == "creator":
self.source = f'{record["manufacturer"]} {record["garmin_product"]} fit'
break
if self.polyline_container:
self.start_time_local, self.end_time_local = parse_datetime_to_local(
self.start_time, self.end_time, self.polyline_container[0]
Expand Down
88 changes: 88 additions & 0 deletions run_page/joyrun_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import argparse
import json
import os
import subprocess
import sys
import time
from collections import namedtuple
from datetime import datetime, timedelta
Expand Down Expand Up @@ -337,12 +339,95 @@ def get_all_joyrun_tracks(self, old_tracks_ids, with_gpx=False):
return tracks


def _generate_svg_profile(athlete, min_grid_distance):
# To generate svg for 'Total' in the left-up map
if not athlete:
# Skip to avoid override
print("Skipping gen_svg. Fill your name with --athlete if you don't want skip")
return
print(
f"Running scripts for [Make svg GitHub profile] with athlete={athlete} min_grid_distance={min_grid_distance}"
)
cmd_args_list = [
[
sys.executable,
"run_page/gen_svg.py",
"--from-db",
"--title",
f"{athlete} Running",
"--type",
"github",
"--athlete",
athlete,
"--special-distance",
"10",
"--special-distance2",
"20",
"--special-color",
"yellow",
"--special-color2",
"red",
"--output",
"assets/github.svg",
"--use-localtime",
"--min-distance",
"0.5",
],
[
sys.executable,
"run_page/gen_svg.py",
"--from-db",
"--title",
f"Over {min_grid_distance} Running",
"--type",
"grid",
"--athlete",
athlete,
"--special-distance",
"20",
"--special-distance2",
"40",
"--special-color",
"yellow",
"--special-color2",
"red",
"--output",
"assets/grid.svg",
"--use-localtime",
"--min-distance",
str(min_grid_distance),
],
[
sys.executable,
"run_page/gen_svg.py",
"--from-db",
"--type",
"circular",
"--use-localtime",
],
]
for cmd_args in cmd_args_list:
subprocess.run(cmd_args, check=True)


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("phone_number_or_uid", help="joyrun phone number or uid")
parser.add_argument(
"identifying_code_or_sid", help="joyrun identifying_code from sms or sid"
)
parser.add_argument(
"--athlete",
dest="athlete",
help="athlete, keep same with {env.ATHLETE}",
)
parser.add_argument(
"--min_grid_distance",
dest="min_grid_distance",
help="min_grid_distance, keep same with {env.MIN_GRID_DISTANCE}",
type=int,
default=10,
)
parser.add_argument(
"--with-gpx",
dest="with_gpx",
Expand Down Expand Up @@ -375,3 +460,6 @@ def get_all_joyrun_tracks(self, old_tracks_ids, with_gpx=False):
activities_list = generator.load()
with open(JSON_FILE, "w") as f:
json.dump(activities_list, f, indent=0)

print("Data export to DB done")
_generate_svg_profile(options.athlete, options.min_grid_distance)
11 changes: 4 additions & 7 deletions run_page/keep_to_strava_sync.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import argparse
import json
import os
from sre_constants import SUCCESS
import time
from collections import namedtuple
import requests
from config import GPX_FOLDER
from Crypto.Cipher import AES
from config import OUTPUT_DIR
from stravalib.exc import ActivityUploadFailed, RateLimitTimeout
from utils import make_strava_client, upload_file_to_strava
from keep_sync import KEEP_DATA_TYPE_API, get_all_keep_tracks
from keep_sync import KEEP_SPORT_TYPES, get_all_keep_tracks
from strava_sync import run_strava_sync

"""
Expand Down Expand Up @@ -72,10 +69,10 @@ def run_keep_sync(email, password, keep_sports_data_api, with_download_gpx=False
)

options = parser.parse_args()
for api in options.sync_types:
for _tpye in options.sync_types:
assert (
api in KEEP_DATA_TYPE_API
), f"{api} are not supported type, please make sure that the type entered in the {KEEP_DATA_TYPE_API}"
_tpye in KEEP_SPORT_TYPES
), f"{_tpye} are not supported type, please make sure that the type entered in the {KEEP_SPORT_TYPES}"
new_tracks = run_keep_sync(
options.phone_number, options.password, options.sync_types, True
)
Expand Down
29 changes: 15 additions & 14 deletions run_page/nike_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,21 @@ class Nike:
def __init__(self, refresh_token):
self.client = httpx.Client()

response = self.client.post(
TOKEN_REFRESH_URL,
headers=NIKE_HEADERS,
json={
"refresh_token": refresh_token,
"client_id": b64decode(NIKE_CLIENT_ID).decode(),
"grant_type": "refresh_token",
"ux_id": b64decode(NIKE_UX_ID).decode(),
},
timeout=60,
)
response.raise_for_status()

access_token = response.json()["access_token"]
# response = self.client.post(
# TOKEN_REFRESH_URL,
# headers=NIKE_HEADERS,
# json={
# "refresh_token": refresh_token,
# "client_id": b64decode(NIKE_CLIENT_ID).decode(),
# "grant_type": "refresh_token",
# "ux_id": b64decode(NIKE_UX_ID).decode(),
# },
# timeout=60,
# )
# response.raise_for_status()
#
# access_token = response.json()["access_token"]
access_token = "The content of 'access_token' that you just copied."
self.client.headers.update({"Authorization": f"Bearer {access_token}"})

def get_activities_since_timestamp(self, timestamp):
Expand Down
Loading

0 comments on commit 9d587ff

Please sign in to comment.