Fixed daily client

main
Ebbe Baß 2025-10-29 13:23:40 +01:00
parent cf3411cded
commit 6da973f37b
1 changed files with 41 additions and 96 deletions

View File

@ -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)