from __future__ import annotations from datetime import datetime from flask import Flask, jsonify, request from app.services import notifications as notif def create_app(crawler) -> Flask: app = Flask(__name__) # Support single crawler or a list of crawlers crawlers = None if isinstance(crawler, (list, tuple)): crawlers = list(crawler) @app.get('/health') def health(): return jsonify({"status": "healthy", "timestamp": datetime.now().isoformat()}) @app.get('/stats') def stats(): if crawlers is not None: return jsonify({ (getattr(c, 'symbol', getattr(c, 'name', f"crawler_{i}")) or f"crawler_{i}"): c.stats for i, c in enumerate(crawlers) }) if crawler: return jsonify(crawler.stats) return jsonify({"error": "Crawler not initialized"}), 500 @app.get('/info') def info(): if crawlers is not None: out = [] for c in crawlers: out.append({ "name": getattr(c, 'name', 'unknown'), "type": c.__class__.__name__, "symbol": getattr(c, 'symbol', None), "schedule": getattr(c.config, 'run_daily_at', None) or f"every {c.config.check_interval}s", }) return jsonify(out) if not crawler: return jsonify({"error": "Crawler not initialized"}), 500 return jsonify({ "name": getattr(crawler, 'name', 'unknown'), "type": crawler.__class__.__name__, "symbol": getattr(crawler, 'symbol', None), "schedule": getattr(crawler.config, 'run_daily_at', None) or f"every {crawler.config.check_interval}s", }) @app.get('/check') def manual_check(): if crawlers is not None: results = [] for c in crawlers: r = c.run_check() or [] results.append({ "symbol": getattr(c, 'symbol', None), "new": len(r) }) return jsonify({"results": results}) if not crawler: return jsonify({"error": "Crawler not initialized"}), 500 result = crawler.run_check() or [] return jsonify({"result": f"Found {len(result)} new picks"}) @app.get('/notify_test') def notify_test(): channel = (request.args.get('channel') or 'email').lower() target = request.args.get('target') test_pick = [notif.build_test_pick()] def _send_for(c): if channel == 'email': if not c.config.email: return {"error": "Email config not set"} # Build subject/body using crawler's formatting hook for consistency if hasattr(c, '_build_email'): subject, body = c._build_email(test_pick) else: subject = f"{getattr(c, 'name', '通知測試')}(測試)" body = notif.format_email_body(test_pick) notif.send_custom_email(subject, body, c.config.email) elif channel == 'webhook': if not c.config.webhook_url: return {"error": "Webhook URL not set"} notif.send_webhook(test_pick, c.config.webhook_url) elif channel == 'discord': if not c.config.discord_webhook: return {"error": "Discord webhook not set"} notif.send_discord(test_pick, c.config.discord_webhook) else: return {"error": f"Unsupported channel: {channel}"} return {"result": f"Test notification sent via {channel}"} if crawlers is not None: results = {} for c in crawlers: key = getattr(c, 'symbol', getattr(c, 'name', 'unknown')) if target and key != target: continue try: results[key] = _send_for(c) except Exception as e: c.logger.error(f"測試通知發送失敗({key}): {e}") results[key] = {"error": str(e)} return jsonify(results) if not crawler: return jsonify({"error": "Crawler not initialized"}), 500 try: res = _send_for(crawler) if 'error' in res: return jsonify(res), 400 return jsonify(res) except Exception as e: crawler.logger.error(f"測試通知發送失敗: {e}") return jsonify({"error": str(e)}), 500 return app