from datetime import datetime import hashlib import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from typing import List, Dict, Optional import requests from app.config import EmailConfig def format_email_body(new_picks: List[Dict]) -> str: body = f"發現 {len(new_picks)} 條新的股票推薦:\n\n" for pick in new_picks: body += f"📊 {pick['title']}\n" if pick.get('link'): body += f"🔗 {pick['link']}\n" body += f"🕒 {pick.get('scraped_at', datetime.now().isoformat())}\n" body += "-" * 60 + "\n" return body def _send_email(subject: str, body: str, cfg: EmailConfig) -> None: msg = MIMEMultipart() msg['From'] = cfg.from_email msg['To'] = cfg.to_email msg['Subject'] = subject msg.attach(MIMEText(body, 'plain', 'utf-8')) if cfg.smtp_security == 'ssl': server = smtplib.SMTP_SSL(cfg.smtp_server, cfg.smtp_port) else: server = smtplib.SMTP(cfg.smtp_server, cfg.smtp_port) server.ehlo() if cfg.smtp_security == 'starttls': server.starttls() server.ehlo() server.login(cfg.username, cfg.password) server.send_message(msg) server.quit() def send_email(new_picks: List[Dict], cfg: EmailConfig) -> None: subject = f"📈 Barron's 新股票推薦 ({len(new_picks)}條)" body = format_email_body(new_picks) _send_email(subject, body, cfg) def send_custom_email(subject: str, body: str, cfg: EmailConfig) -> None: _send_email(subject, body, cfg) def send_webhook(new_picks: List[Dict], url: str) -> None: message = f"🚨 發現 {len(new_picks)} 條新的 Barron's 股票推薦!\n\n" for pick in new_picks[:5]: message += f"• {pick['title']}\n" if pick.get('link'): message += f" {pick['link']}\n" message += "\n" payload = {"text": message} requests.post(url, json=payload, timeout=10) def send_text_webhook(message: str, url: str) -> None: payload = {"text": message} requests.post(url, json=payload, timeout=10) def send_discord(new_picks: List[Dict], webhook: str) -> None: embed = { "title": "📈 Barron's 新股票推薦", "description": f"發現 {len(new_picks)} 條新推薦", "color": 0x00ff00, "fields": [], } for pick in new_picks[:5]: embed["fields"].append({ "name": pick['title'][:256], "value": (pick.get('link') or '無連結')[:1024], "inline": False, }) requests.post(webhook, json={"embeds": [embed]}, timeout=10) def send_text_discord(title: str, description: str, lines: List[str], webhook: str) -> None: embed = { "title": title, "description": description, "color": 0x00ff00, "fields": [], } for line in lines[:10]: embed["fields"].append({ "name": line[:256], "value": "\u200b", "inline": False, }) requests.post(webhook, json={"embeds": [embed]}, timeout=10) def build_test_pick() -> Dict: return { 'title': f"[測試] Barron's 通知發送 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", 'link': 'https://example.com/test', 'scraped_at': datetime.now().isoformat(), 'hash': hashlib.md5(str(datetime.now().timestamp()).encode()).hexdigest()[:8], }