aafd487b83
Token-authenticated custom component exposing live per-club member counts as sensors under a single "West Wood Club" device, fed by one coordinator polling `/v1/Clubs/WhoIsInCount`. Packaged via `buildHomeAssistantComponent` with a flake package + overlay so it can be used in `services.home-assistant.customComponents`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
85 lines
2.4 KiB
Python
Executable File
85 lines
2.4 KiB
Python
Executable File
#!/usr/bin/env python
|
|
"""Log in to the West Wood Club (PerfectGym) API and print the bearer token.
|
|
|
|
The token is written to stdout (and nothing else), so it can be piped to a file:
|
|
|
|
python login.py > token.txt
|
|
|
|
Credentials are read from the WESTWOOD_EMAIL and WESTWOOD_PASSWORD environment
|
|
variables; any that are missing are prompted for on the terminal. Prompts and
|
|
errors go to stderr, keeping stdout clean for the token.
|
|
"""
|
|
|
|
import getpass
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
BASE_URL = 'https://goapi2.perfectgym.com'
|
|
WHITE_LABEL_ID = '7d073db5-0ef8-4d78-89ec-4a8bebaf4cbc'
|
|
USER_AGENT = (
|
|
'West Wood Club/1.28.3.0 '
|
|
'(com.perfectgym.perfectgymgo2.westwoodclub; build:1028003000; Android 16)'
|
|
)
|
|
|
|
|
|
def log_in(email: str, password: str) -> str:
|
|
"""Return the bearer token (the value for the Authorization header)."""
|
|
body = json.dumps(
|
|
{
|
|
'email': email,
|
|
'password': password,
|
|
'clientApplicationInfo': {
|
|
'type': 'whitelabel',
|
|
'whiteLabelId': WHITE_LABEL_ID,
|
|
},
|
|
}
|
|
).encode()
|
|
|
|
request = urllib.request.Request(
|
|
f'{BASE_URL}/v1/Authorize/LogInWithEmail',
|
|
data=body,
|
|
method='POST',
|
|
headers={
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json; charset=UTF-8',
|
|
'Accept-Language': 'en',
|
|
'X-Go-App-Platform': 'Android',
|
|
'X-Go-App-Version': '1.28.3',
|
|
'X-Go-White-Label-ID': WHITE_LABEL_ID,
|
|
'User-Agent': USER_AGENT,
|
|
},
|
|
)
|
|
|
|
with urllib.request.urlopen(request) as response:
|
|
payload = json.load(response)
|
|
|
|
if payload.get('errors'):
|
|
raise SystemExit(f'login failed: {payload["errors"]}')
|
|
|
|
data = payload.get('data') or {}
|
|
token = data.get('token')
|
|
if not token:
|
|
raise SystemExit(f'no token in response: {payload}')
|
|
return token
|
|
|
|
|
|
def main() -> None:
|
|
email = os.environ.get('WESTWOOD_EMAIL') or input('Email: ')
|
|
password = os.environ.get('WESTWOOD_PASSWORD') or getpass.getpass('Password: ')
|
|
|
|
try:
|
|
token = log_in(email, password)
|
|
except urllib.error.HTTPError as exc:
|
|
raise SystemExit(f'HTTP {exc.code}: {exc.read().decode("utf-8", "replace")}')
|
|
except urllib.error.URLError as exc:
|
|
raise SystemExit(f'request failed: {exc.reason}')
|
|
|
|
print(token)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|