diff --git a/client/daily_client.py b/client/daily_client.py index 3c86739..0e9c286 100644 --- a/client/daily_client.py +++ b/client/daily_client.py @@ -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 argparse +import os +import json from pathlib import Path - # Ensure a .env file exists next to this script and load variables from it. def ensure_env_file(env_path: str, defaults: dict): """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: print(f"Failed to create env file {env_path}: {e}") - def load_env_file(env_path: str): """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: print(f"Failed to read env file {env_path}: {e}") - # Path to .env in the same folder as this script _env_path = os.path.join(os.path.dirname(__file__), '.env') _defaults = { @@ -76,7 +52,6 @@ _defaults = { ensure_env_file(_env_path, _defaults) load_env_file(_env_path) - # Where to fetch a quote from (API) 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) API_TOKEN = os.environ.get('SCHLOTER_API_TOKEN', _defaults['SCHLOTER_API_TOKEN']) - def get_quote(): """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.raise_for_status() data = resp.json() - # API returns {'id': ..., 'quote': '...'} - return data.get("quote") + return data.get("quote", "No quote found.") +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"} + 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 = { - "@type": "MessageCard", - "@context": "https://schema.org/extensions", - "themeColor": color, - "summary": title, - "sections": [ + # keep text for flows that read triggerBody()['text'] + "text": quote, + # attachments array for flows that expect triggerBody()['attachments'] + "attachments": [ { - "activityTitle": f"**{title}**", - "text": message + "contentType": "application/vnd.microsoft.card.adaptive", + "content": adaptive_card } ] } try: - response = requests.post(webhook_url, headers=headers, data=json.dumps(payload), timeout=10) - # Microsoft Teams incoming webhooks may return 200 or 202 for success - if response.status_code in (200, 202): - text = response.text.strip() - if text: - print(f"✅ Message sent successfully! Response: {response.status_code} - {text}") - else: - print(f"✅ Message sent successfully! Response: {response.status_code}") - return True - else: - print(f"❌ Failed to send message: {response.status_code} - {response.text}") - return False - except Exception as e: - print(f"⚠️ Error sending message: {e}") + resp = requests.post(TEAMS_WEBHOOK_URL, headers=headers, data=json.dumps(payload), timeout=10) + except requests.RequestException as e: + print(f"Network error posting to Teams webhook: {e}") + return False + + # Don't raise; print detailed failure info for debugging + if resp.status_code in (200, 202): + print(f"Posted to Teams webhook successfully (status {resp.status_code}).") + if resp.text: + print("Response body:", resp.text) + return True + else: + print(f"Failed to post to Teams webhook: {resp.status_code}") + print("Response body:", resp.text) 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__": - # Allow a debug/dry-run mode so we don't have to call the schloter API. - p = argparse.ArgumentParser(description="Fetch quote (or use local debug quote) and post to Teams webhook") - p.add_argument('--no-api', action='store_true', help='Do not call the schloter API; use a local quote instead') - 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) + # Fetch a quote and post it to Teams. Use env vars to override defaults. + quote = get_quote() + print("Quote fetched:", quote)