alexa-webhook / app.py
SillyHippy's picture
Update app.py
ff4e8e9 verified
Raw History Blame Contribute Delete
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 ---
@app.post("/{trigger_name}")
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}")