import json from pathlib import Path import jinja2 import requests from requests import RequestException class Athletico: def fetch_episodes_for_access_code(self, access_code: str) -> dict: """ Given a home access code, fetch exercise information :param access_code: the access code to a home exercise program from Athletico :return: a dict containing the exercise information """ session = requests.Session() home_url = "https://athleticopt.medbridgego.com" fetch_episodes_url = "https://athleticopt.medbridgego.com/api/v4/lite/episodes/" session.get(home_url) csrf_token = session.cookies.get("csrf_cookie_name") session.post( url="https://athleticopt.medbridgego.com/register_token", data={ "token": access_code, "X-CSRF-Token": csrf_token, "verify_access_code": "Verify+Access+Code", }, ) response = session.get(fetch_episodes_url) if response.ok and response.json() and response.json().get("status"): return response.json().get("episodes") else: raise RequestException( f"Request Failed, {response.status_code}: {response.reason}" ) def render(self, template: str, output: str, context: dict) -> str: """ Renders an HTML page. :param template: Path to Jinja template :param output: Destination for .html file :param context: The context to be passed to the render :return: Path to html file """ output_fpath = Path(output) template_fpath = Path(output) environment = jinja2.Environment(loader=jinja2.FileSystemLoader()) template = environment.get_template(str(template_fpath.absolute())) page = template.render(**context) output_fpath.mkdir(parents=True, exist_ok=True) with output_fpath.open("w") as f: f.write(page) return str(output_fpath) def save_episodes(self, episodes: list, destination: str = ".") -> None: """ Outputs episodes to disk in a folder structure ├── exercises/ │ ├── {EXERCISE_ID}/ │ │ ├── {EXERCISE_ID}.json │ │ ├── {EXERCISE_ID}.txt │ │ ├── description.html │ │ └── video_file.m3u8 │ └── {EXERCISE_ID}/ │ ├── {EXERCISE_ID}.json │ ├── {EXERCISE_ID}.txt │ ├── description.html │ └── video_file.m3u8 └── programs/ └── {ACCESS_TOKEN}/ └── episodes/ └── {EPISODE_ID}.json :param episodes: List of episode data, matches output of fetch_episodes_for_access_code :param destination: Filepath to save to, defaults to current directory :return: None """ destination_fpath = Path(destination) destination_fpath.joinpath() for episode in episodes: episode_id = str(episode["id"]) program = episode["program"] exercises = [] episode_fpath = destination_fpath.joinpath( "programs", episode["token"], "episodes" ) episode_fpath.mkdir(parents=True, exist_ok=True) for exercise in program["program_exercises"]: exercises.append(exercise) exercise_id = str(exercise["id"]) exercise_fpath = destination_fpath.joinpath("exercises", exercise_id) thumbnails_fpath = exercise_fpath.joinpath("thumbnails") thumbnails_fpath.mkdir(parents=True, exist_ok=True) for i, image in enumerate(exercise["exercise"]["thumbnails"]): r = requests.get(image["image_filepath"]) fname = image["image_filepath"].split("/")[-1] with thumbnails_fpath.joinpath(fname).open("wb") as f: f.write(r.content) json_fpath = exercise_fpath.joinpath(exercise_id).with_suffix(".json") with json_fpath.open("w") as f: json.dump(exercise, f) txt_fpath = exercise_fpath.joinpath(exercise_id).with_suffix(".txt") with txt_fpath.open("w") as f: f.write(exercise["name"]) if exercise["exercise"]["video_file"]: video_fpath = exercise_fpath.joinpath("video_file").with_suffix( ".m3u8" ) with video_fpath.open("wb") as f: r = requests.get("http:" + exercise["exercise"]["video_file"]) f.write(r.content) description_fpath = exercise_fpath.joinpath("description").with_suffix( ".html" ) with description_fpath.open("w") as f: f.write(exercise["exercise"]["description"]) episode_json_fpath = episode_fpath.joinpath(episode_id).with_suffix(".json") with episode_json_fpath.open("w") as f: json.dump(episode, fp=f) return if __name__ == "__main__": pass