Python lib/script to export Foreflight logbook
1from typing import Any, cast
2import requests
3from fflogex.cli import main
4
5
6class ForeflightLogbookExporter:
7 _session: requests.Session
8
9 def __init__(self):
10 self._session = requests.Session()
11
12 def login(self, username: str, password: str):
13 """Login to Foreflight.
14
15 :param str username: Foreflight username
16 :param str password: Foreflight password
17 """
18
19 self._session.cookies.clear()
20 resp = self._session.get("https://plan.foreflight.com/")
21 assert resp.ok
22 resp = self._session.post(
23 "https://plan.foreflight.com/auth/api/login",
24 json={"username": username, "password": password, "sessionType": "PRIVATE"},
25 headers={"X-Xsrftoken": self._session.cookies.get("_xsrf")},
26 )
27 assert resp.ok
28
29 def request_export(self) -> str:
30 """Request Logbook Export on Foreflight.
31
32 Foreflight will start the asynchronous logbook export process and return a request ID. We will
33 use the ID to poll the export progress and subsequently get the link to the CSV file.
34
35 :return: logbook export request ID
36 :rtype: str
37 :raises AssertionError: if we failed to get the requestId
38 """
39
40 out: Any = self._session.get( # pyright: ignore[reportAny,reportExplicitAny]
41 "https://plan.foreflight.com/logbook/api/export/csv"
42 ).json()
43 assert out["result"]["requestId"] is not None
44 requestId = cast(str, out["result"]["requestId"])
45
46 return requestId
47
48 def get_link(self, requestId: str) -> str | None:
49 """Poll for download link.
50
51 Use the request ID returned from `request_export` to poll for the export progress and download
52 link.
53
54 :return: link to the CSV if completed, None if still in process.
55 :rtype: str | None
56 :raises AssertionError: if we failed to get the link
57 """
58
59 out: Any = self._session.get( # pyright: ignore[reportAny,reportExplicitAny]
60 f"https://plan.foreflight.com/logbook/api/export/status/csv/{requestId}"
61 ).json()
62 if out["result"]["status"] == "FINISHED":
63 assert out["result"]["link"] is not None
64 return cast(str, out["result"]["link"])
65 else:
66 return None
67
68 def download_link(self, link: str) -> str:
69 """Download the CSV file
70
71 Provided as a convenience method. You can also just use any tool to download the link returned
72 from the `get_link` method as it is usually a signed S3 URL.
73
74 :return: content of the logbook CSV export
75 :rtype: str
76 """
77
78 return self._session.get(link).text
79
80
81def main_cli():
82 main()