schloter/client/daily_client.py

183 lines
6.0 KiB
Python

#!/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
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."""
p = Path(env_path)
if not p.exists():
try:
p.parent.mkdir(parents=True, exist_ok=True)
with p.open('w', encoding='utf-8') as f:
for k, v in defaults.items():
f.write(f"{k}={v}\n")
print(f"Created env file: {env_path}")
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.
Supports simple lines, ignores blank lines and lines starting with '#'.
"""
p = Path(env_path)
if not p.exists():
return
try:
with p.open('r', encoding='utf-8') as f:
for raw in f:
line = raw.strip()
if not line or line.startswith('#'):
continue
if '=' not in line:
continue
k, v = line.split('=', 1)
k = k.strip()
v = v.strip().strip('"').strip("'")
if k and k not in os.environ:
os.environ[k] = v
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 = {
'SCHLOTER_API_URL': 'http://schloter.api.ping-mee.de/quotes/random',
'TEAMS_WEBHOOK_URL': 'https://outlook.office.com/webhook/REPLACE_WITH_YOUR_WEBHOOK',
'SCHLOTER_API_TOKEN': '?2%?fK+@%Y9wy!f6'
}
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'])
# Microsoft Teams Incoming Webhook URL (Workflows / connectors)
TEAMS_WEBHOOK_URL = os.environ.get('TEAMS_WEBHOOK_URL', _defaults['TEAMS_WEBHOOK_URL'])
# 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.
Returns the quote string or raises on HTTP error.
"""
headers = {"Authorization": f"Bearer {API_TOKEN}"}
resp = requests.get(API_URL, headers=headers, timeout=10)
resp.raise_for_status()
data = resp.json()
# API returns {'id': ..., 'quote': '...'}
return data.get("quote")
def send_teams_message(webhook_url, title, message, color="0076D7"):
"""
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"}
payload = {
"@type": "MessageCard",
"@context": "https://schema.org/extensions",
"themeColor": color,
"summary": title,
"sections": [
{
"activityTitle": f"**{title}**",
"text": message
}
]
}
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}")
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)