Spaces:
Sleeping
Sleeping
Download app.py from SillyHippy/alexa-webhook: direct link, hf CLI and curl.
- Browser
- Download file 6.41 kB
-
https://huggingface.co/spaces/SillyHippy/alexa-webhook/resolve/main/app.py
- Command line
-
hf download hf://spaces/SillyHippy/alexa-webhook/app.py
-
curl -L -o app.py https://huggingface.co/spaces/SillyHippy/alexa-webhook/resolve/main/app.py
6.41 kB
| from fastapi import FastAPI, Request, HTTPException | |
| import requests | |
| import json | |
| import uuid | |
| from datetime import datetime, timezone | |
| import os # Import the 'os' module | |
| app = FastAPI() | |
| # --- CONFIGURATION --- | |
| # Use Hugging Face Secrets for sensitive data | |
| CLIENT_ID = "amzn1.application-oa2-client.bc4475f5b85148fa8c1329e4bb6da5e9" # Your *NEW* ALEXA Client ID (from Custom Skill) | |
| CLIENT_SECRET = os.environ.get("CLIENT_SECRET") # Get *ALEXA* Client Secret from environment variable | |
| ALEXA_EVENT_GATEWAY = "https://api.amazonalexa.com/v3/events" | |
| # --- DEVICE MAPPING --- | |
| TRIGGERS = { | |
| "Trigger1": {"endpointId": "device-001", "description": "Webhook Trigger 1", "state": "NOT_DETECTED"}, | |
| "Trigger2": {"endpointId": "device-002", "description": "Webhook Trigger 2", "state": "NOT_DETECTED"}, | |
| "Trigger3": {"endpointId": "device-003", "description": "Webhook Trigger 3", "state": "NOT_DETECTED"}, | |
| "Trigger4": {"endpointId": "device-004", "description": "Webhook Trigger 4", "state": "NOT_DETECTED"}, | |
| "Trigger5": {"endpointId": "device-005", "description": "Webhook Trigger 5", "state": "NOT_DETECTED"}, | |
| "Trigger6": {"endpointId": "device-006", "description": "Webhook Trigger 6", "state": "NOT_DETECTED"}, | |
| "Trigger7": {"endpointId": "device-007", "description": "Webhook Trigger 7", "state": "NOT_DETECTED"}, | |
| "Trigger8": {"endpointId": "device-008", "description": "Webhook Trigger 8", "state": "NOT_DETECTED"}, | |
| "Trigger9": {"endpointId": "device-009", "description": "Webhook Trigger 9", "state": "NOT_DETECTED"}, | |
| "Trigger10": {"endpointId": "device-010", "description": "Webhook Trigger 10", "state": "NOT_DETECTED"}, | |
| } | |
| # --- TOKEN MANAGEMENT (Simple - In-Memory for Now) --- | |
| TOKEN_DATA = { | |
| "access_token": None, | |
| "expires_at": None, # Timestamp when the token expires | |
| } | |
| # --- HELPER FUNCTION: Get LWA Access Token --- | |
| async def get_lwa_access_token(): | |
| """Fetches or refreshes the LWA access token.""" | |
| global TOKEN_DATA | |
| # Check if we have a token and if it's still valid | |
| if TOKEN_DATA["access_token"] and TOKEN_DATA["expires_at"] and datetime.now(timezone.utc).timestamp() < TOKEN_DATA["expires_at"]: | |
| return TOKEN_DATA["access_token"] | |
| # Get a new token | |
| token_url = "https://api.amazon.com/auth/o2/token" | |
| data = { | |
| "grant_type": "client_credentials", | |
| "client_id": CLIENT_ID, | |
| "client_secret": CLIENT_SECRET, | |
| "scope": "alexa::proactive_events", | |
| } | |
| # --- DEBUGGING LOGS --- | |
| print("----- LWA Token Request Data -----") | |
| print(data) | |
| print("---------------------------------") | |
| print(f"----- CLIENT_SECRET Value: {CLIENT_SECRET} -----") | |
| # --- END OF DEBUGGING LOGS --- | |
| try: | |
| response = requests.post(token_url, data=data) | |
| response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx) | |
| token_response = response.json() | |
| TOKEN_DATA["access_token"] = token_response["access_token"] | |
| # Calculate expiry time (subtract a small buffer to be safe) | |
| TOKEN_DATA["expires_at"] = datetime.now(timezone.utc).timestamp() + token_response["expires_in"] - 60 | |
| return TOKEN_DATA["access_token"] | |
| except requests.exceptions.RequestException as e: | |
| print(f"Error getting LWA token: {e}") | |
| raise HTTPException(status_code=500, detail="Failed to get LWA access token") | |
| # --- HELPER FUNCTION: Send ChangeReport to Alexa --- | |
| async def send_change_report(endpoint_id, property_name, property_value): | |
| """Sends a ChangeReport event to Alexa.""" | |
| try: | |
| access_token = await get_lwa_access_token() | |
| except HTTPException: # Re-raise exception from get_lwa_access_token | |
| raise | |
| headers = { | |
| "Authorization": f"Bearer {access_token}", | |
| "Content-Type": "application/json", | |
| } | |
| change_report = { | |
| "event": { | |
| "header": { | |
| "namespace": "Alexa", | |
| "name": "ChangeReport", | |
| "messageId": str(uuid.uuid4()), | |
| "payloadVersion": "3", | |
| }, | |
| "endpoint": { | |
| "endpointId": endpoint_id, | |
| }, | |
| "payload": { | |
| "change": { | |
| "cause": {"type": "PHYSICAL_INTERACTION"}, | |
| "properties": [ | |
| { | |
| "namespace": "Alexa.ContactSensor", | |
| "name": property_name, | |
| "value": property_value, | |
| "timeOfSample": datetime.now(timezone.utc).isoformat(), | |
| "uncertaintyInMilliseconds": 0, | |
| } | |
| ], | |
| } | |
| }, | |
| } | |
| } | |
| try: | |
| response = requests.post( | |
| ALEXA_EVENT_GATEWAY, headers=headers, data=json.dumps(change_report) | |
| ) | |
| response.raise_for_status() | |
| print(f"ChangeReport sent for {endpoint_id}. Response: {response.status_code} - {response.text}") # Log full response | |
| except requests.exceptions.RequestException as e: | |
| print(f"Error sending ChangeReport: {e}") | |
| raise HTTPException(status_code=500, detail=f"Failed to send ChangeReport: {e}") | |
| # --- WEBHOOK ENDPOINT --- | |
| async def alexa_webhook(trigger_name: str, request: Request): | |
| data = await request.json() | |
| print(f"Received request for {trigger_name}: {data}") | |
| if trigger_name in TRIGGERS: | |
| endpoint_id = TRIGGERS[trigger_name]["endpointId"] | |
| # Toggle the state | |
| current_state = TRIGGERS[trigger_name]["state"] | |
| new_state = "NOT_DETECTED" if current_state == "DETECTED" else "DETECTED" | |
| TRIGGERS[trigger_name]["state"] = new_state | |
| try: | |
| await send_change_report(endpoint_id, "detectionState", new_state) | |
| except HTTPException as e: | |
| return e # Return the HTTPException directly | |
| return { | |
| "event": { | |
| "header": { | |
| "namespace": "Alexa", | |
| "name": "Response", | |
| "payloadVersion": "3", | |
| "messageId": str(uuid.uuid4()) | |
| }, | |
| "payload": {"message": f"{trigger_name} activated! New state: {new_state}"} | |
| } | |
| } | |
| else: | |
| raise HTTPException(status_code=404, detail=f"Unknown trigger: {trigger_name}") |