Add Home Assistant integration

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>
This commit is contained in:
2026-06-14 00:08:15 +01:00
parent ea1eccb4a5
commit aafd487b83
12 changed files with 488 additions and 32 deletions
Regular → Executable
+28 -28
View File
@@ -17,11 +17,11 @@ import sys
import urllib.error
import urllib.request
BASE_URL = "https://goapi2.perfectgym.com"
WHITE_LABEL_ID = "7d073db5-0ef8-4d78-89ec-4a8bebaf4cbc"
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)"
'West Wood Club/1.28.3.0 '
'(com.perfectgym.perfectgymgo2.westwoodclub; build:1028003000; Android 16)'
)
@@ -29,56 +29,56 @@ 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,
'email': email,
'password': password,
'clientApplicationInfo': {
'type': 'whitelabel',
'whiteLabelId': WHITE_LABEL_ID,
},
}
).encode()
request = urllib.request.Request(
f"{BASE_URL}/v1/Authorize/LogInWithEmail",
f'{BASE_URL}/v1/Authorize/LogInWithEmail',
data=body,
method="POST",
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,
'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']}")
if payload.get('errors'):
raise SystemExit(f'login failed: {payload["errors"]}')
data = payload.get("data") or {}
token = data.get("token")
data = payload.get('data') or {}
token = data.get('token')
if not token:
raise SystemExit(f"no token in response: {payload}")
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: ")
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')}")
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}")
raise SystemExit(f'request failed: {exc.reason}')
print(token)
if __name__ == "__main__":
if __name__ == '__main__':
main()