Initial commit: Oil Trading Bot (MT5, WTI)
Headless FastAPI-Backend (server.py + core/engine.py) mit Mobile-PWA (web/), Strategie-/Backtest-Suite und Doku. Secrets, DB, Logs und Laufzeit-State sind via .gitignore ausgeschlossen; Config-Vorlage: oil_widget_config.ini.example. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
core/mailer.py — E-Mail via Microsoft Graph (client-credentials)
|
||||
=================================================================
|
||||
Sendet HTML-Mails über die Graph-App-Registrierung in `[graph]`
|
||||
(tenant_id/client_id/client_secret). Absender = `[graph] sender` oder
|
||||
Default mailagent@hocks.eu. Ersetzt den PowerShell-Umweg (send_daily_report.ps1)
|
||||
durch reines Python — so kann die Engine den Tagesreport selbst verschicken.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from core.logger import get_logger
|
||||
|
||||
log = get_logger("mail")
|
||||
|
||||
_TOKEN_URL = "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token"
|
||||
_SENDMAIL = "https://graph.microsoft.com/v1.0/users/{sender}/sendMail"
|
||||
_DEFAULT_SENDER = "mailagent@hocks.eu"
|
||||
|
||||
|
||||
def send_graph_mail(graph_cfg, subject: str, html: str, to_addr: str) -> bool:
|
||||
"""True bei Erfolg. `to_addr` darf mehrere Adressen kommagetrennt enthalten.
|
||||
Fail-safe: loggt + gibt False zurück, wirft nie."""
|
||||
import requests
|
||||
g = graph_cfg or {}
|
||||
tenant = (g.get("tenant_id") or "").strip()
|
||||
cid = (g.get("client_id") or "").strip()
|
||||
secret = (g.get("client_secret") or "").strip()
|
||||
sender = (g.get("sender") or _DEFAULT_SENDER).strip()
|
||||
recipients = [{"emailAddress": {"address": a.strip()}}
|
||||
for a in (to_addr or "").split(",") if a.strip()]
|
||||
if not (tenant and cid and secret) or not recipients:
|
||||
log.warning("Graph-Config/Empfänger unvollständig — keine E-Mail")
|
||||
return False
|
||||
try:
|
||||
tok = requests.post(
|
||||
_TOKEN_URL.format(tenant=tenant),
|
||||
data={"client_id": cid, "client_secret": secret,
|
||||
"scope": "https://graph.microsoft.com/.default",
|
||||
"grant_type": "client_credentials"}, timeout=30)
|
||||
tok.raise_for_status()
|
||||
access = tok.json().get("access_token")
|
||||
if not access:
|
||||
log.warning("Graph: kein access_token"); return False
|
||||
r = requests.post(
|
||||
_SENDMAIL.format(sender=sender),
|
||||
headers={"Authorization": "Bearer " + access},
|
||||
json={"message": {
|
||||
"subject": subject,
|
||||
"body": {"contentType": "HTML", "content": html},
|
||||
"toRecipients": recipients,
|
||||
"from": {"emailAddress": {"address": sender}}},
|
||||
"saveToSentItems": True}, timeout=30)
|
||||
if r.status_code in (200, 202):
|
||||
log.info(f"E-Mail an {to_addr}: {subject}")
|
||||
return True
|
||||
log.warning(f"Graph sendMail {r.status_code}: {r.text[:200]}")
|
||||
return False
|
||||
except Exception as e:
|
||||
log.warning(f"send_graph_mail: {e}")
|
||||
return False
|
||||
Reference in New Issue
Block a user