2024-10-10 22:53:39 +00:00
|
|
|
"""Withings API fetcher.
|
|
|
|
|
|
|
|
Supports downloading activity summary from the [Withings
|
|
|
|
API](https://developer.withings.com/api-reference/) using the [non-official
|
|
|
|
Withings API Python Client](https://pypi.org/project/withings-api/).
|
|
|
|
"""
|
|
|
|
|
|
|
|
import dataclasses
|
2024-10-10 22:54:01 +00:00
|
|
|
import datetime
|
2024-10-10 22:53:39 +00:00
|
|
|
import logging
|
2024-10-10 22:54:01 +00:00
|
|
|
import pickle
|
2024-10-10 22:53:39 +00:00
|
|
|
import subprocess
|
2024-10-10 22:54:01 +00:00
|
|
|
from pathlib import Path
|
2024-10-10 22:53:39 +00:00
|
|
|
|
2024-10-10 22:54:01 +00:00
|
|
|
import withings_api
|
|
|
|
from withings_api.common import CredentialsType
|
2024-10-10 22:53:39 +00:00
|
|
|
|
|
|
|
from personal_data import secrets
|
|
|
|
from personal_data.data import DeduplicateMode, Scraper
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
CREDENTIALS_FILE = Path('secrets/withings_oath_creds')
|
|
|
|
|
2024-10-10 22:54:01 +00:00
|
|
|
|
2024-10-10 22:53:39 +00:00
|
|
|
def save_credentials(credentials: CredentialsType) -> None:
|
|
|
|
"""Save credentials to a file."""
|
2024-10-10 22:54:01 +00:00
|
|
|
logger.info('Saving credentials in: %s', CREDENTIALS_FILE)
|
|
|
|
with open(CREDENTIALS_FILE, 'wb') as file_handle:
|
2024-10-10 22:53:39 +00:00
|
|
|
pickle.dump(credentials, file_handle)
|
|
|
|
|
|
|
|
|
|
|
|
def load_credentials() -> CredentialsType:
|
|
|
|
"""Load credentials from a file."""
|
2024-10-10 22:54:01 +00:00
|
|
|
logger.info('Using credentials saved in: %s', CREDENTIALS_FILE)
|
2024-10-10 22:53:39 +00:00
|
|
|
try:
|
2024-10-10 22:54:01 +00:00
|
|
|
with open(CREDENTIALS_FILE, 'rb') as file_handle:
|
2024-10-10 22:53:39 +00:00
|
|
|
return pickle.load(file_handle)
|
|
|
|
except FileNotFoundError:
|
|
|
|
return None
|
|
|
|
|
2024-10-10 22:54:01 +00:00
|
|
|
|
2024-10-10 22:53:39 +00:00
|
|
|
@dataclasses.dataclass(frozen=True)
|
|
|
|
class WithingsActivityScraper(Scraper):
|
|
|
|
dataset_name = 'withings_activity'
|
|
|
|
deduplicate_mode = DeduplicateMode.BY_ALL_COLUMNS
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def requires_cfscrape() -> bool:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def oauth_flow(self) -> CredentialsType:
|
|
|
|
if creds := load_credentials():
|
|
|
|
return creds
|
|
|
|
|
|
|
|
auth = withings_api.WithingsAuth(
|
|
|
|
client_id=secrets.WITHINGS_CLIENTID,
|
|
|
|
consumer_secret=secrets.WITHINGS_SECRET,
|
|
|
|
callback_uri=secrets.WITHINGS_CALLBACK_URI,
|
|
|
|
scope=(
|
|
|
|
withings_api.AuthScope.USER_ACTIVITY,
|
|
|
|
withings_api.AuthScope.USER_METRICS,
|
|
|
|
withings_api.AuthScope.USER_INFO,
|
|
|
|
withings_api.AuthScope.USER_SLEEP_EVENTS,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
authorize_url = auth.get_authorize_url()
|
|
|
|
|
|
|
|
subprocess.run(['firefox', '--new-tab', authorize_url])
|
|
|
|
credentials_code = input('Please insert your code here: ').strip()
|
|
|
|
|
|
|
|
creds = auth.get_credentials(credentials_code)
|
|
|
|
save_credentials(creds)
|
|
|
|
return creds
|
|
|
|
|
|
|
|
def scrape(self):
|
|
|
|
credentials = self.oauth_flow()
|
|
|
|
|
|
|
|
# Now you are ready to make calls for data.
|
|
|
|
api = withings_api.WithingsApi(credentials)
|
|
|
|
|
2024-10-10 22:54:01 +00:00
|
|
|
start = datetime.date.today() - datetime.timedelta(days=200)
|
2024-10-10 22:53:39 +00:00
|
|
|
end = datetime.date.today()
|
|
|
|
|
|
|
|
activity_result = api.measure_get_activity(
|
2024-10-10 22:54:01 +00:00
|
|
|
startdateymd=start,
|
|
|
|
enddateymd=end,
|
2024-10-10 22:53:39 +00:00
|
|
|
)
|
|
|
|
for activity in activity_result.activities:
|
|
|
|
sample = dict(activity)
|
|
|
|
sample['date'] = activity.date.date()
|
|
|
|
del sample['timezone'], sample['is_tracker']
|
|
|
|
yield sample
|
|
|
|
del activity, sample
|