Fixed daily client
parent
cf3411cded
commit
6da973f37b
|
|
@ -1,30 +1,8 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
client/daily_client.py
|
|
||||||
|
|
||||||
Fetch a quote from the API and post it to a Microsoft Teams channel via a webhook.
|
|
||||||
|
|
||||||
Behavior:
|
|
||||||
- Ensures a `.env` file exists next to this script with sensible defaults (if missing).
|
|
||||||
- Loads KEY=VALUE pairs from the `.env` into the environment (if not already set).
|
|
||||||
- Requests a random quote from the API using a Bearer token.
|
|
||||||
- Posts the quote to Teams as a MessageCard (title: "Daily Quote").
|
|
||||||
|
|
||||||
Exit codes:
|
|
||||||
0 = success
|
|
||||||
1 = no quote returned
|
|
||||||
2 = failed to fetch quote
|
|
||||||
3 = failed to post to Teams
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
import requests
|
import requests
|
||||||
import argparse
|
import os
|
||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
# Ensure a .env file exists next to this script and load variables from it.
|
# Ensure a .env file exists next to this script and load variables from it.
|
||||||
def ensure_env_file(env_path: str, defaults: dict):
|
def ensure_env_file(env_path: str, defaults: dict):
|
||||||
"""Create an env file at env_path with defaults if it doesn't exist."""
|
"""Create an env file at env_path with defaults if it doesn't exist."""
|
||||||
|
|
@ -39,7 +17,6 @@ def ensure_env_file(env_path: str, defaults: dict):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to create env file {env_path}: {e}")
|
print(f"Failed to create env file {env_path}: {e}")
|
||||||
|
|
||||||
|
|
||||||
def load_env_file(env_path: str):
|
def load_env_file(env_path: str):
|
||||||
"""Load KEY=VALUE lines from env_path into os.environ if not already set.
|
"""Load KEY=VALUE lines from env_path into os.environ if not already set.
|
||||||
|
|
||||||
|
|
@ -64,7 +41,6 @@ def load_env_file(env_path: str):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to read env file {env_path}: {e}")
|
print(f"Failed to read env file {env_path}: {e}")
|
||||||
|
|
||||||
|
|
||||||
# Path to .env in the same folder as this script
|
# Path to .env in the same folder as this script
|
||||||
_env_path = os.path.join(os.path.dirname(__file__), '.env')
|
_env_path = os.path.join(os.path.dirname(__file__), '.env')
|
||||||
_defaults = {
|
_defaults = {
|
||||||
|
|
@ -76,7 +52,6 @@ _defaults = {
|
||||||
ensure_env_file(_env_path, _defaults)
|
ensure_env_file(_env_path, _defaults)
|
||||||
load_env_file(_env_path)
|
load_env_file(_env_path)
|
||||||
|
|
||||||
|
|
||||||
# Where to fetch a quote from (API)
|
# Where to fetch a quote from (API)
|
||||||
API_URL = os.environ.get('SCHLOTER_API_URL', _defaults['SCHLOTER_API_URL'])
|
API_URL = os.environ.get('SCHLOTER_API_URL', _defaults['SCHLOTER_API_URL'])
|
||||||
|
|
||||||
|
|
@ -86,7 +61,6 @@ TEAMS_WEBHOOK_URL = os.environ.get('TEAMS_WEBHOOK_URL', _defaults['TEAMS_WEBHOOK
|
||||||
# Static token for the API (can be set via environment variable)
|
# Static token for the API (can be set via environment variable)
|
||||||
API_TOKEN = os.environ.get('SCHLOTER_API_TOKEN', _defaults['SCHLOTER_API_TOKEN'])
|
API_TOKEN = os.environ.get('SCHLOTER_API_TOKEN', _defaults['SCHLOTER_API_TOKEN'])
|
||||||
|
|
||||||
|
|
||||||
def get_quote():
|
def get_quote():
|
||||||
"""Fetch a quote from the API using a Bearer token.
|
"""Fetch a quote from the API using a Bearer token.
|
||||||
|
|
||||||
|
|
@ -96,87 +70,58 @@ def get_quote():
|
||||||
resp = requests.get(API_URL, headers=headers, timeout=10)
|
resp = requests.get(API_URL, headers=headers, timeout=10)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
# API returns {'id': ..., 'quote': '...'}
|
return data.get("quote", "No quote found.")
|
||||||
return data.get("quote")
|
|
||||||
|
|
||||||
|
def post_to_teams(quote):
|
||||||
|
"""Post the quote to a Teams Workflows (incoming webhook) URL.
|
||||||
|
|
||||||
def send_teams_message(webhook_url, title, message, color="0076D7"):
|
To satisfy Power Automate flows that expect triggerBody()['attachments'],
|
||||||
|
this posts both a plain 'text' property and an 'attachments' array containing
|
||||||
|
an Adaptive Card. Returns True on success (200 or 202), False otherwise.
|
||||||
"""
|
"""
|
||||||
Sends a formatted message to a Microsoft Teams channel using a webhook as a MessageCard.
|
|
||||||
|
|
||||||
Treats HTTP 200 and 202 as success and prints response details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
|
|
||||||
|
adaptive_card = {
|
||||||
|
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||||
|
"type": "AdaptiveCard",
|
||||||
|
"version": "1.2",
|
||||||
|
"body": [
|
||||||
|
{"type": "TextBlock", "size": "Medium", "weight": "Bolder", "text": "Daily Quote"},
|
||||||
|
{"type": "TextBlock", "wrap": False, "text": quote}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"@type": "MessageCard",
|
# keep text for flows that read triggerBody()['text']
|
||||||
"@context": "https://schema.org/extensions",
|
"text": quote,
|
||||||
"themeColor": color,
|
# attachments array for flows that expect triggerBody()['attachments']
|
||||||
"summary": title,
|
"attachments": [
|
||||||
"sections": [
|
|
||||||
{
|
{
|
||||||
"activityTitle": f"**{title}**",
|
"contentType": "application/vnd.microsoft.card.adaptive",
|
||||||
"text": message
|
"content": adaptive_card
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.post(webhook_url, headers=headers, data=json.dumps(payload), timeout=10)
|
resp = requests.post(TEAMS_WEBHOOK_URL, headers=headers, data=json.dumps(payload), timeout=10)
|
||||||
# Microsoft Teams incoming webhooks may return 200 or 202 for success
|
except requests.RequestException as e:
|
||||||
if response.status_code in (200, 202):
|
print(f"Network error posting to Teams webhook: {e}")
|
||||||
text = response.text.strip()
|
return False
|
||||||
if text:
|
|
||||||
print(f"✅ Message sent successfully! Response: {response.status_code} - {text}")
|
# Don't raise; print detailed failure info for debugging
|
||||||
else:
|
if resp.status_code in (200, 202):
|
||||||
print(f"✅ Message sent successfully! Response: {response.status_code}")
|
print(f"Posted to Teams webhook successfully (status {resp.status_code}).")
|
||||||
return True
|
if resp.text:
|
||||||
else:
|
print("Response body:", resp.text)
|
||||||
print(f"❌ Failed to send message: {response.status_code} - {response.text}")
|
return True
|
||||||
return False
|
else:
|
||||||
except Exception as e:
|
print(f"Failed to post to Teams webhook: {resp.status_code}")
|
||||||
print(f"⚠️ Error sending message: {e}")
|
print("Response body:", resp.text)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def post_to_teams(quote):
|
|
||||||
"""Wrapper to post the daily quote as a MessageCard to Teams."""
|
|
||||||
title = "Daily Quote"
|
|
||||||
return send_teams_message(TEAMS_WEBHOOK_URL, title=title, message=quote)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Allow a debug/dry-run mode so we don't have to call the schloter API.
|
# Fetch a quote and post it to Teams. Use env vars to override defaults.
|
||||||
p = argparse.ArgumentParser(description="Fetch quote (or use local debug quote) and post to Teams webhook")
|
quote = get_quote()
|
||||||
p.add_argument('--no-api', action='store_true', help='Do not call the schloter API; use a local quote instead')
|
print("Quote fetched:", quote)
|
||||||
p.add_argument('--local-quote', help='Local quote text to use when --no-api is set')
|
|
||||||
args = p.parse_args()
|
|
||||||
|
|
||||||
no_api_env = os.environ.get('SCHLOTER_DEBUG_NO_API', '').lower() in ('1', 'true', 'yes')
|
|
||||||
use_no_api = args.no_api or no_api_env
|
|
||||||
|
|
||||||
if use_no_api:
|
|
||||||
# Choose local quote from CLI, env, or a sensible default
|
|
||||||
quote = args.local_quote or os.environ.get('SCHLOTER_LOCAL_QUOTE') or 'Debug quote: no API call performed.'
|
|
||||||
print('DEBUG: Running without calling schloter API; using local quote.')
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
quote = get_quote()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Failed to fetch quote from API: {e}")
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
if not quote:
|
|
||||||
print("No quote available")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print("Quote to send:", quote)
|
|
||||||
|
|
||||||
ok = post_to_teams(quote)
|
|
||||||
if not ok:
|
|
||||||
print("Failed to post quote to Teams")
|
|
||||||
sys.exit(3)
|
|
||||||
|
|
||||||
print("Quote successfully posted to Teams.")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue