Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/baiqi2021_aviaaidaily1.0.json
T

1 line
46 KiB
JSON

{"repo": "baiqi2021/aviaaidaily1.0", "files": {"trendradar/core/loader.py": {"0eee582e33a13e4135d9d8337ae46cb426a7a4b9": {"t": 1785745989.6434953, "n": 14002, "h": "db9a6ef736dd7d5c", "c": "# coding=utf-8\n\"\"\"\n配置加载模块\n\n负责从 YAML 配置文件和环境变量加载配置。\n\"\"\"\n\nimport os\nfrom pathlib import Path\nfrom typing import Dict, Any, Optional\n\nimport yaml\n\nfrom .config import parse_multi_account_config, validate_paired_configs\n\n\ndef _get_env_bool(key: str, default: bool = False) -> Optional[bool]:\n \"\"\"从环境变量获取布尔值,如果未设置返回 None\"\"\"\n value = os.environ.get(key, \"\").strip().lower()\n if not value:\n return None\n return value in (\"true\", \"1\")\n\n\ndef _get_env_int(key: str, default: int = 0) -> int:\n \"\"\"从环境变量获取整数值\"\"\"\n value = os.environ.get(key, \"\").strip()\n if not value:\n return default\n try:\n return int(value)\n except ValueError:\n return default\n\n\ndef _get_env_str(key: str, default: str = \"\") -> str:\n \"\"\"从环境变量获取字符串值\"\"\"\n return os.environ.get(key, \"\").strip() or default\n\n\ndef _load_app_config(config_data: Dict) -> Dict:\n \"\"\"加载应用配置\"\"\"\n app_config = config_data.get(\"app\", {})\n return {\n \"VERSION_CHECK_URL\": app_config.get(\"version_check_url\", \"\"),\n \"SHOW_VERSION_UPDATE\": app_config.get(\"show_version_update\", True),\n \"TIMEZONE\": _get_env_str(\"TIMEZONE\") or app_config.get(\"timezone\", \"Asia/Shanghai\"),\n }\n\n\ndef _load_crawler_config(config_data: Dict) -> Dict:\n \"\"\"加载爬虫配置\"\"\"\n crawler_config = config_data.get(\"crawler\", {})\n enable_crawler_env = _get_env_bool(\"ENABLE_CRAWLER\")\n return {\n \"REQUEST_INTERVAL\": crawler_config.get(\"request_interval\", 100),\n \"USE_PROXY\": crawler_config.get(\"use_proxy\", False),\n \"DEFAULT_PROXY\": crawler_config.get(\"default_proxy\", \"\"),\n \"ENABLE_CRAWLER\": enable_crawler_env if enable_crawler_env is not None else crawler_config.get(\"enable_crawler\", True),\n }\n\n\ndef _load_report_config(config_data: Dict) -> Dict:\n \"\"\"加载报告配置\"\"\"\n report_config = config_data.get(\"report\", {})\n\n # 环境变量覆盖\n sort_by_position_env = _get_env_bool(\"SORT_BY_POSITION_FIRST\")\n reverse_content_env = _get_env_bool(\"REVERSE_CONTENT_ORDER\")\n max_news_env = _get_env_int(\"MAX_NEWS_PER_KEYWORD\")\n\n return {\n \"REPORT_MODE\": _get_env_str(\"REPORT_MODE\") or report_config.get(\"mode\", \"daily\"),\n \"RANK_THRESHOLD\": report_config.get(\"rank_threshold\", 10),\n \"SORT_BY_POSITION_FIRST\": sort_by_position_env if sort_by_position_env is not None else report_config.get(\"sort_by_position_first\", False),\n \"MAX_NEWS_PER_KEYWORD\": max_news_env or report_config.get(\"max_news_per_keyword\", 0),\n \"REVERSE_CONTENT_ORDER\": reverse_content_env if reverse_content_env is not None else report_config.get(\"reverse_content_order\", False),\n }\n\n\ndef _load_notification_config(config_data: Dict) -> Dict:\n \"\"\"加载通知配置\"\"\"\n notification = config_data.get(\"notification\", {})\n enable_notification_env = _get_env_bool(\"ENABLE_NOTIFICATION\")\n\n return {\n \"ENABLE_NOTIFICATION\": enable_notification_env if enable_notification_env is not None else notification.get(\"enable_notification\", True),\n \"MESSAGE_BATCH_SIZE\": notification.get(\"message_batch_size\", 4000),\n \"DINGTALK_BATCH_SIZE\": notification.get(\"dingtalk_batch_size\", 20000),\n \"FEISHU_BATCH_SIZE\": notification.get(\"feishu_batch_size\", 29000),\n \"BARK_BATCH_SIZE\": notification.get(\"bark_batch_size\", 3600),\n \"SLACK_BATCH_SIZE\": notification.get(\"slack_batch_size\", 4000),\n \"BATCH_SEND_INTERVAL\": notification.get(\"batch_send_interval\", 1.0),\n \"FEISHU_MESSAGE_SEPARATOR\": notification.get(\"feishu_message_separator\", \"---\"),\n \"MAX_ACCOUNTS_PER_CHANNEL\": _get_env_int(\"MAX_ACCOUNTS_PER_CHANNEL\") or notification.get(\"max_accounts_per_channel\", 3),\n }\n\n\ndef _load_push_window_config(config_data: Dict) -> Dict:\n \"\"\"加载推送窗口配置\"\"\"\n notification = config_data.get(\"notification\", {})\n push_window = notification.get(\"push_window\", {})\n time_range = push_window.get(\"time_range\", {})\n\n enabled_env = _get_env_bool(\"PUSH_WINDOW_ENABLED\")\n once_per_day_env = _get_env_bool(\"PUSH_WINDOW_ONCE_PER_DAY\")\n\n return {\n \"ENABLED\": enabled_env if enabled_env is not None else push_window.get(\"enabled\", False),\n \"TIME_RANGE\": {\n \"START\": _get_env_str(\"PUSH_WINDOW_START\") or time_range.get(\"start\", \"08:00\"),\n \"END\": _get_env_str(\"PUSH_WINDOW_END\") or time_range.get(\"end\", \"22:00\"),\n },\n \"ONCE_PER_DAY\": once_per_day_env if once_per_day_env is not None else push_window.get(\"once_per_day\", True),\n }\n\n\ndef _load_weight_config(config_data: Dict) -> Dict:\n \"\"\"加载权重配置\"\"\"\n weight = config_data.get(\"weight\", {})\n return {\n \"RANK_WEIGHT\": weight.get(\"rank_weight\", 1.0),\n \"FREQUENCY_WEIGHT\": weight.get(\"frequency_weight\", 1.0),\n \"HOTNESS_WEIGHT\": weight.get(\"hotness_weight\", 1.0),\n }\n\n\ndef _load_storage_config(config_data: Dict) -> Dict:\n \"\"\"加载存储配置\"\"\"\n storage = config_data.get(\"storage\", {})\n formats = storage.get(\"formats\", {})\n local = storage.get(\"local\", {})\n remote = storage.get(\"remote\", {})\n pull = storage.get(\"pull\", {})\n\n txt_enabled_env = _get_env_bool(\"STORAGE_TXT_ENABLED\")\n html_enabled_env = _get_env_bool(\"STORAGE_HTML_ENABLED\")\n pull_enabled_env = _get_env_bool(\"PULL_ENABLED\")\n\n return {\n \"BACKEND\": _get_env_str(\"STORAGE_BACKEND\") or storage.get(\"backend\", \"auto\"),\n \"FORMATS\": {\n \"SQLITE\": formats.get(\"sqlite\", True),\n \"TXT\": txt_enabled_env if txt_enabled_env is not None else formats.get(\"txt\", True),\n \"HTML\": html_enabled_env if html_enabled_env is not None else formats.get(\"html\", True),\n },\n \"LOCAL\": {\n \"DATA_DIR\": local.get(\"data_dir\", \"output\"),\n \"RETENTION_DAYS\": _get_env_int(\"LOCAL_RETENTION_DAYS\") or local.get(\"retention_days\", 0),\n },\n \"REMOTE\": {\n \"ENDPOINT_URL\": _get_env_str(\"S3_ENDPOINT_URL\") or remote.get(\"endpoint_url\", \"\"),\n \"BUCKET_NAME\": _get_env_str(\"S3_BUCKET_NAME\") or remote.get(\"bucket_name\", \"\"),\n \"ACCESS_KEY_ID\": _get_env_str(\"S3_ACCESS_KEY_ID\") or remote.get(\"access_key_id\", \"\"),\n \"SECRET_ACCESS_KEY\": _get_env_str(\"S3_SECRET_ACCESS_KEY\") or remote.get(\"secret_access_key\", \"\"),\n \"REGION\": _get_env_str(\"S3_REGION\") or remote.get(\"region\", \"\"),\n \"RETENTION_DAYS\": _get_env_int(\"REMOTE_RETENTION_DAYS\") or remote.get(\"retention_days\", 0),\n },\n \"PULL\": {\n \"ENABLED\": pull_enabled_env if pull_enabled_env is not None else pull.get(\"enabled\", False),\n \"DAYS\": _get_env_int(\"PULL_DAYS\") or pull.get(\"days\", 7),\n },\n }\n\n\ndef _load_webhook_config(config_data: Dict) -> Dict:\n \"\"\"加载 Webhook 配置\"\"\"\n notification = config_data.get(\"notification\", {})\n webhooks = notification.get(\"webhooks\", {})\n\n return {\n # 飞书\n \"FEISHU_WEBHOOK_URL\": _get_env_str(\"FEISHU_WEBHOOK_URL\") or webhooks.get(\"feishu_url\", \"\"),\n # 钉钉\n \"DINGTALK_WEBHOOK_URL\": _get_env_str(\"DINGTALK_WEBHOOK_URL\") or webhooks.get(\"dingtalk_url\", \"\"),\n # 企业微信\n \"WEWORK_WEBHOOK_URL\": _get_env_str(\"WEWORK_WEBHOOK_URL\") or webhooks.get(\"wework_url\", \"\"),\n \"WEWORK_MSG_TYPE\": _get_env_str(\"WEWORK_MSG_TYPE\") or webhooks.get(\"wework_msg_type\", \"markdown\"),\n # Telegram\n \"TELEGRAM_BOT_TOKEN\": _get_env_str(\"TELEGRAM_BOT_TOKEN\") or webhooks.get(\"telegram_bot_token\", \"\"),\n \"TELEGRAM_CHAT_ID\": _get_env_str(\"TELEGRAM_CHAT_ID\") or webhooks.get(\"telegram_chat_id\", \"\"),\n # 邮件\n \"EMAIL_FROM\": _get_env_str(\"EMAIL_FROM\") or webhooks.get(\"email_from\", \"\"),\n \"EMAIL_PASSWORD\": _get_env_str(\"EMAIL_PASSWORD\") or webhooks.get(\"email_password\", \"\"),\n \"EMAIL_TO\": _get_env_str(\"EMAIL_TO\") or webhooks.get(\"email_to\", \"\"),\n \"EMAIL_SMTP_SERVER\": _get_env_str(\"EMAIL_SMTP_SERVER\") or webhooks.get(\"email_smtp_server\", \"\"),\n \"EMAIL_SMTP_PORT\": _get_env_str(\"EMAIL_SMTP_PORT\") or webhooks.get(\"email_smtp_port\", \"\"),\n # ntfy\n \"NTFY_SERVER_URL\": _get_env_str(\"NTFY_SERVER_URL\") or webhooks.get(\"ntfy_server_url\") or \"https://ntfy.sh\",\n \"NTFY_TOPIC\": _get_env_str(\"NTFY_TOPIC\") or webhooks.get(\"ntfy_topic\", \"\"),\n \"NTFY_TOKEN\": _get_env_str(\"NTFY_TOKEN\") or webhooks.get(\"ntfy_token\", \"\"),\n # Bark\n \"BARK_URL\": _get_env_str(\"BARK_URL\") or webhooks.get(\"bark_url\", \"\"),\n # Slack\n \"SLACK_WEBHOOK_URL\": _get_env_str(\"SLACK_WEBHOOK_URL\") or webhooks.get(\"slack_webhook_url\", \"\"),\n }\n\n\ndef _print_notification_sources(config: Dict) -> None:\n \"\"\"打印通知渠道配置来源信息\"\"\"\n notification_sources = []\n max_accounts = config[\"MAX_ACCOUNTS_PER_CHANNEL\"]\n\n if config[\"FEISHU_WEBHOOK_URL\"]:\n accounts = parse_multi_account_config(config[\"FEISHU_WEBHOOK_URL\"])\n count = min(len(accounts), max_accounts)\n source = \"环境变量\" if os.environ.get(\"FEISHU_WEBHOOK_URL\") else \"配置文件\"\n notification_sources.append(f\"飞书({source}, {count}个账号)\")\n\n if config[\"DINGTALK_WEBHOOK_URL\"]:\n accounts = parse_multi_account_config(config[\"DINGTALK_WEBHOOK_URL\"])\n count = min(len(accounts), max_accounts)\n source = \"环境变量\" if os.environ.get(\"DINGTALK_WEBHOOK_URL\") else \"配置文件\"\n notification_sources.append(f\"钉钉({source}, {count}个账号)\")\n\n if config[\"WEWORK_WEBHOOK_URL\"]:\n accounts = parse_multi_account_config(config[\"WEWORK_WEBHOOK_URL\"])\n count = min(len(accounts), max_accounts)\n source = \"环境变量\" if os.environ.get(\"WEWORK_WEBHOOK_URL\") else \"配置文件\"\n notification_sources.append(f\"企业微信({source}, {count}个账号)\")\n\n if config[\"TELEGRAM_BOT_TOKEN\"] and config[\"TELEGRAM_CHAT_ID\"]:\n tokens = parse_multi_account_config(config[\"TELEGRAM_BOT_TOKEN\"])\n chat_ids = parse_multi_account_config(config[\"TELEGRAM_CHAT_ID\"])\n valid, count = validate_paired_configs(\n {\"bot_token\": tokens, \"chat_id\": chat_ids},\n \"Telegram\",\n required_keys=[\"bot_token\", \"chat_id\"]\n )\n if valid and count > 0:\n count = min(count, max_accounts)\n token_source = \"环境变量\" if os.environ.get(\"TELEGRAM_BOT_TOKEN\") else \"配置文件\"\n notification_sources.append(f\"Telegram({token_source}, {count}个账号)\")\n\n if config[\"EMAIL_FROM\"] and config[\"EMAIL_PASSWORD\"] and config[\"EMAIL_TO\"]:\n from_source = \"环境变量\" if os.environ.get(\"EMAIL_FROM\") else \"配置文件\"\n notification_sources.append(f\"邮件({from_source})\")\n\n if config[\"NTFY_SERVER_URL\"] and config[\"NTFY_TOPIC\"]:\n topics = parse_multi_account_config(config[\"NTFY_TOPIC\"])\n tokens = parse_multi_account_config(config[\"NTFY_TOKEN\"])\n if tokens:\n valid, count = validate_paired_configs(\n {\"topic\": topics, \"token\": tokens},\n \"ntfy\"\n )\n if valid and count > 0:\n count = min(count, max_accounts)\n server_source = \"环境变量\" if os.environ.get(\"NTFY_SERVER_URL\") else \"配置文件\"\n notification_sources.append(f\"ntfy({server_source}, {count}个账号)\")\n else:\n count = min(len(topics), max_accounts)\n server_source = \"环境变量\" if os.environ.get(\"NTFY_SERVER_URL\") else \"配置文件\"\n notification_sources.append(f\"ntfy({server_source}, {count}个账号)\")\n\n if config[\"BARK_URL\"]:\n accounts = parse_multi_account_config(config[\"BARK_URL\"])\n count = min(len(accounts), max_accounts)\n bark_source = \"环境变量\" if os.environ.get(\"BARK_URL\") else \"配置文件\"\n notification_sources.append(f\"Bark({bark_source}, {count}个账号)\")\n\n if config[\"SLACK_WEBHOOK_URL\"]:\n accounts = parse_multi_account_config(config[\"SLACK_WEBHOOK_URL\"])\n count = min(len(accounts), max_accounts)\n slack_source = \"环境变量\" if os.environ.get(\"SLACK_WEBHOOK_URL\") else \"配置文件\"\n notification_sources.append(f\"Slack({slack_source}, {count}个账号)\")\n\n if notification_sources:\n print(f\"通知渠道配置来源: {', '.join(notification_sources)}\")\n print(f\"每个渠道最大账号数: {max_accounts}\")\n else:\n print(\"未配置任何通知渠道\")\n\n\ndef _load_llm_config(config_data: Dict) -> Dict:\n \"\"\"加载 LLM 配置\"\"\"\n llm_config = config_data.get(\"llm\", {})\n return {\n \"ENABLED\": _get_env_bool(\"LLM_ENABLED\") or llm_config.get(\"enabled\", False),\n \"PROVIDER\": _get_env_str(\"LLM_PROVIDER\") or llm_config.get(\"provider\", \"deepseek\"),\n \"API_KEY\": _get_env_str(\"LLM_API_KEY\") or llm_config.get(\"api_key\", \"\"),\n \"BASE_URL\": _get_env_str(\"LLM_BASE_URL\") or llm_config.get(\"base_url\", \"https://api.deepseek.com\"),\n \"MODEL\": _get_env_str(\"LLM_MODEL\") or llm_config.get(\"model\", \"deepseek-chat\"),\n }\n\n\ndef load_config(config_path: Optional[str] = None) -> Dict[str, Any]:\n \"\"\"\n 加载配置文件\n\n Args:\n config_path: 配置文件路径,默认从环境变量 CONFIG_PATH 获取或使用 config/config.yaml\n\n Returns:\n 包含所有配置的字典\n\n Raises:\n FileNotFoundError: 配置文件不存在\n \"\"\"\n if config_path is None:\n config_path = os.environ.get(\"CONFIG_PATH\", \"config/config.yaml\")\n\n if not Path(config_path).exists():\n raise FileNotFoundError(f\"配置文件 {config_path} 不存在\")\n\n with open(config_path, \"r\", encoding=\"utf-8\") as f:\n config_data = yaml.safe_load(f)\n\n print(f\"配置文件加载成功: {config_path}\")\n\n # 合并所有配置\n config = {}\n\n # 应用配置\n config.update(_load_app_config(config_data))\n\n # 爬虫配置\n config.update(_load_crawler_config(config_data))\n\n # 报告配置\n config.update(_load_report_config(config_data))\n\n # 通知配置\n config.update(_load_notification_config(config_data))\n\n # 推送窗口配置\n config[\"PUSH_WINDOW\"] = _load_push_window_config(config_data)\n\n # 权重配置\n config[\"WEIGHT_CONFIG\"] = _load_weight_config(config_data)\n\n # 平台配置\n config[\"PLATFORMS\"] = config_data.get(\"platforms\", [])\n\n # 存储配置\n config[\"STORAGE\"] = _load_storage_config(config_data)\n\n # LLM 配置\n config[\"LLM\"] = _load_llm_config(config_data)\n\n # Webhook 配置\n config.update(_load_webhook_config(config_data))\n\n # 打印通知渠道配置来源\n _print_notification_sources(config)\n\n return config\n"}, "2c780f6a02ec11a943b60ba9ae327325c9562090": {"t": 1785758580.3406956, "n": 14002, "h": "db9a6ef736dd7d5c", "c": "# coding=utf-8\n\"\"\"\n配置加载模块\n\n负责从 YAML 配置文件和环境变量加载配置。\n\"\"\"\n\nimport os\nfrom pathlib import Path\nfrom typing import Dict, Any, Optional\n\nimport yaml\n\nfrom .config import parse_multi_account_config, validate_paired_configs\n\n\ndef _get_env_bool(key: str, default: bool = False) -> Optional[bool]:\n \"\"\"从环境变量获取布尔值,如果未设置返回 None\"\"\"\n value = os.environ.get(key, \"\").strip().lower()\n if not value:\n return None\n return value in (\"true\", \"1\")\n\n\ndef _get_env_int(key: str, default: int = 0) -> int:\n \"\"\"从环境变量获取整数值\"\"\"\n value = os.environ.get(key, \"\").strip()\n if not value:\n return default\n try:\n return int(value)\n except ValueError:\n return default\n\n\ndef _get_env_str(key: str, default: str = \"\") -> str:\n \"\"\"从环境变量获取字符串值\"\"\"\n return os.environ.get(key, \"\").strip() or default\n\n\ndef _load_app_config(config_data: Dict) -> Dict:\n \"\"\"加载应用配置\"\"\"\n app_config = config_data.get(\"app\", {})\n return {\n \"VERSION_CHECK_URL\": app_config.get(\"version_check_url\", \"\"),\n \"SHOW_VERSION_UPDATE\": app_config.get(\"show_version_update\", True),\n \"TIMEZONE\": _get_env_str(\"TIMEZONE\") or app_config.get(\"timezone\", \"Asia/Shanghai\"),\n }\n\n\ndef _load_crawler_config(config_data: Dict) -> Dict:\n \"\"\"加载爬虫配置\"\"\"\n crawler_config = config_data.get(\"crawler\", {})\n enable_crawler_env = _get_env_bool(\"ENABLE_CRAWLER\")\n return {\n \"REQUEST_INTERVAL\": crawler_config.get(\"request_interval\", 100),\n \"USE_PROXY\": crawler_config.get(\"use_proxy\", False),\n \"DEFAULT_PROXY\": crawler_config.get(\"default_proxy\", \"\"),\n \"ENABLE_CRAWLER\": enable_crawler_env if enable_crawler_env is not None else crawler_config.get(\"enable_crawler\", True),\n }\n\n\ndef _load_report_config(config_data: Dict) -> Dict:\n \"\"\"加载报告配置\"\"\"\n report_config = config_data.get(\"report\", {})\n\n # 环境变量覆盖\n sort_by_position_env = _get_env_bool(\"SORT_BY_POSITION_FIRST\")\n reverse_content_env = _get_env_bool(\"REVERSE_CONTENT_ORDER\")\n max_news_env = _get_env_int(\"MAX_NEWS_PER_KEYWORD\")\n\n return {\n \"REPORT_MODE\": _get_env_str(\"REPORT_MODE\") or report_config.get(\"mode\", \"daily\"),\n \"RANK_THRESHOLD\": report_config.get(\"rank_threshold\", 10),\n \"SORT_BY_POSITION_FIRST\": sort_by_position_env if sort_by_position_env is not None else report_config.get(\"sort_by_position_first\", False),\n \"MAX_NEWS_PER_KEYWORD\": max_news_env or report_config.get(\"max_news_per_keyword\", 0),\n \"REVERSE_CONTENT_ORDER\": reverse_content_env if reverse_content_env is not None else report_config.get(\"reverse_content_order\", False),\n }\n\n\ndef _load_notification_config(config_data: Dict) -> Dict:\n \"\"\"加载通知配置\"\"\"\n notification = config_data.get(\"notification\", {})\n enable_notification_env = _get_env_bool(\"ENABLE_NOTIFICATION\")\n\n return {\n \"ENABLE_NOTIFICATION\": enable_notification_env if enable_notification_env is not None else notification.get(\"enable_notification\", True),\n \"MESSAGE_BATCH_SIZE\": notification.get(\"message_batch_size\", 4000),\n \"DINGTALK_BATCH_SIZE\": notification.get(\"dingtalk_batch_size\", 20000),\n \"FEISHU_BATCH_SIZE\": notification.get(\"feishu_batch_size\", 29000),\n \"BARK_BATCH_SIZE\": notification.get(\"bark_batch_size\", 3600),\n \"SLACK_BATCH_SIZE\": notification.get(\"slack_batch_size\", 4000),\n \"BATCH_SEND_INTERVAL\": notification.get(\"batch_send_interval\", 1.0),\n \"FEISHU_MESSAGE_SEPARATOR\": notification.get(\"feishu_message_separator\", \"---\"),\n \"MAX_ACCOUNTS_PER_CHANNEL\": _get_env_int(\"MAX_ACCOUNTS_PER_CHANNEL\") or notification.get(\"max_accounts_per_channel\", 3),\n }\n\n\ndef _load_push_window_config(config_data: Dict) -> Dict:\n \"\"\"加载推送窗口配置\"\"\"\n notification = config_data.get(\"notification\", {})\n push_window = notification.get(\"push_window\", {})\n time_range = push_window.get(\"time_range\", {})\n\n enabled_env = _get_env_bool(\"PUSH_WINDOW_ENABLED\")\n once_per_day_env = _get_env_bool(\"PUSH_WINDOW_ONCE_PER_DAY\")\n\n return {\n \"ENABLED\": enabled_env if enabled_env is not None else push_window.get(\"enabled\", False),\n \"TIME_RANGE\": {\n \"START\": _get_env_str(\"PUSH_WINDOW_START\") or time_range.get(\"start\", \"08:00\"),\n \"END\": _get_env_str(\"PUSH_WINDOW_END\") or time_range.get(\"end\", \"22:00\"),\n },\n \"ONCE_PER_DAY\": once_per_day_env if once_per_day_env is not None else push_window.get(\"once_per_day\", True),\n }\n\n\ndef _load_weight_config(config_data: Dict) -> Dict:\n \"\"\"加载权重配置\"\"\"\n weight = config_data.get(\"weight\", {})\n return {\n \"RANK_WEIGHT\": weight.get(\"rank_weight\", 1.0),\n \"FREQUENCY_WEIGHT\": weight.get(\"frequency_weight\", 1.0),\n \"HOTNESS_WEIGHT\": weight.get(\"hotness_weight\", 1.0),\n }\n\n\ndef _load_storage_config(config_data: Dict) -> Dict:\n \"\"\"加载存储配置\"\"\"\n storage = config_data.get(\"storage\", {})\n formats = storage.get(\"formats\", {})\n local = storage.get(\"local\", {})\n remote = storage.get(\"remote\", {})\n pull = storage.get(\"pull\", {})\n\n txt_enabled_env = _get_env_bool(\"STORAGE_TXT_ENABLED\")\n html_enabled_env = _get_env_bool(\"STORAGE_HTML_ENABLED\")\n pull_enabled_env = _get_env_bool(\"PULL_ENABLED\")\n\n return {\n \"BACKEND\": _get_env_str(\"STORAGE_BACKEND\") or storage.get(\"backend\", \"auto\"),\n \"FORMATS\": {\n \"SQLITE\": formats.get(\"sqlite\", True),\n \"TXT\": txt_enabled_env if txt_enabled_env is not None else formats.get(\"txt\", True),\n \"HTML\": html_enabled_env if html_enabled_env is not None else formats.get(\"html\", True),\n },\n \"LOCAL\": {\n \"DATA_DIR\": local.get(\"data_dir\", \"output\"),\n \"RETENTION_DAYS\": _get_env_int(\"LOCAL_RETENTION_DAYS\") or local.get(\"retention_days\", 0),\n },\n \"REMOTE\": {\n \"ENDPOINT_URL\": _get_env_str(\"S3_ENDPOINT_URL\") or remote.get(\"endpoint_url\", \"\"),\n \"BUCKET_NAME\": _get_env_str(\"S3_BUCKET_NAME\") or remote.get(\"bucket_name\", \"\"),\n \"ACCESS_KEY_ID\": _get_env_str(\"S3_ACCESS_KEY_ID\") or remote.get(\"access_key_id\", \"\"),\n \"SECRET_ACCESS_KEY\": _get_env_str(\"S3_SECRET_ACCESS_KEY\") or remote.get(\"secret_access_key\", \"\"),\n \"REGION\": _get_env_str(\"S3_REGION\") or remote.get(\"region\", \"\"),\n \"RETENTION_DAYS\": _get_env_int(\"REMOTE_RETENTION_DAYS\") or remote.get(\"retention_days\", 0),\n },\n \"PULL\": {\n \"ENABLED\": pull_enabled_env if pull_enabled_env is not None else pull.get(\"enabled\", False),\n \"DAYS\": _get_env_int(\"PULL_DAYS\") or pull.get(\"days\", 7),\n },\n }\n\n\ndef _load_webhook_config(config_data: Dict) -> Dict:\n \"\"\"加载 Webhook 配置\"\"\"\n notification = config_data.get(\"notification\", {})\n webhooks = notification.get(\"webhooks\", {})\n\n return {\n # 飞书\n \"FEISHU_WEBHOOK_URL\": _get_env_str(\"FEISHU_WEBHOOK_URL\") or webhooks.get(\"feishu_url\", \"\"),\n # 钉钉\n \"DINGTALK_WEBHOOK_URL\": _get_env_str(\"DINGTALK_WEBHOOK_URL\") or webhooks.get(\"dingtalk_url\", \"\"),\n # 企业微信\n \"WEWORK_WEBHOOK_URL\": _get_env_str(\"WEWORK_WEBHOOK_URL\") or webhooks.get(\"wework_url\", \"\"),\n \"WEWORK_MSG_TYPE\": _get_env_str(\"WEWORK_MSG_TYPE\") or webhooks.get(\"wework_msg_type\", \"markdown\"),\n # Telegram\n \"TELEGRAM_BOT_TOKEN\": _get_env_str(\"TELEGRAM_BOT_TOKEN\") or webhooks.get(\"telegram_bot_token\", \"\"),\n \"TELEGRAM_CHAT_ID\": _get_env_str(\"TELEGRAM_CHAT_ID\") or webhooks.get(\"telegram_chat_id\", \"\"),\n # 邮件\n \"EMAIL_FROM\": _get_env_str(\"EMAIL_FROM\") or webhooks.get(\"email_from\", \"\"),\n \"EMAIL_PASSWORD\": _get_env_str(\"EMAIL_PASSWORD\") or webhooks.get(\"email_password\", \"\"),\n \"EMAIL_TO\": _get_env_str(\"EMAIL_TO\") or webhooks.get(\"email_to\", \"\"),\n \"EMAIL_SMTP_SERVER\": _get_env_str(\"EMAIL_SMTP_SERVER\") or webhooks.get(\"email_smtp_server\", \"\"),\n \"EMAIL_SMTP_PORT\": _get_env_str(\"EMAIL_SMTP_PORT\") or webhooks.get(\"email_smtp_port\", \"\"),\n # ntfy\n \"NTFY_SERVER_URL\": _get_env_str(\"NTFY_SERVER_URL\") or webhooks.get(\"ntfy_server_url\") or \"https://ntfy.sh\",\n \"NTFY_TOPIC\": _get_env_str(\"NTFY_TOPIC\") or webhooks.get(\"ntfy_topic\", \"\"),\n \"NTFY_TOKEN\": _get_env_str(\"NTFY_TOKEN\") or webhooks.get(\"ntfy_token\", \"\"),\n # Bark\n \"BARK_URL\": _get_env_str(\"BARK_URL\") or webhooks.get(\"bark_url\", \"\"),\n # Slack\n \"SLACK_WEBHOOK_URL\": _get_env_str(\"SLACK_WEBHOOK_URL\") or webhooks.get(\"slack_webhook_url\", \"\"),\n }\n\n\ndef _print_notification_sources(config: Dict) -> None:\n \"\"\"打印通知渠道配置来源信息\"\"\"\n notification_sources = []\n max_accounts = config[\"MAX_ACCOUNTS_PER_CHANNEL\"]\n\n if config[\"FEISHU_WEBHOOK_URL\"]:\n accounts = parse_multi_account_config(config[\"FEISHU_WEBHOOK_URL\"])\n count = min(len(accounts), max_accounts)\n source = \"环境变量\" if os.environ.get(\"FEISHU_WEBHOOK_URL\") else \"配置文件\"\n notification_sources.append(f\"飞书({source}, {count}个账号)\")\n\n if config[\"DINGTALK_WEBHOOK_URL\"]:\n accounts = parse_multi_account_config(config[\"DINGTALK_WEBHOOK_URL\"])\n count = min(len(accounts), max_accounts)\n source = \"环境变量\" if os.environ.get(\"DINGTALK_WEBHOOK_URL\") else \"配置文件\"\n notification_sources.append(f\"钉钉({source}, {count}个账号)\")\n\n if config[\"WEWORK_WEBHOOK_URL\"]:\n accounts = parse_multi_account_config(config[\"WEWORK_WEBHOOK_URL\"])\n count = min(len(accounts), max_accounts)\n source = \"环境变量\" if os.environ.get(\"WEWORK_WEBHOOK_URL\") else \"配置文件\"\n notification_sources.append(f\"企业微信({source}, {count}个账号)\")\n\n if config[\"TELEGRAM_BOT_TOKEN\"] and config[\"TELEGRAM_CHAT_ID\"]:\n tokens = parse_multi_account_config(config[\"TELEGRAM_BOT_TOKEN\"])\n chat_ids = parse_multi_account_config(config[\"TELEGRAM_CHAT_ID\"])\n valid, count = validate_paired_configs(\n {\"bot_token\": tokens, \"chat_id\": chat_ids},\n \"Telegram\",\n required_keys=[\"bot_token\", \"chat_id\"]\n )\n if valid and count > 0:\n count = min(count, max_accounts)\n token_source = \"环境变量\" if os.environ.get(\"TELEGRAM_BOT_TOKEN\") else \"配置文件\"\n notification_sources.append(f\"Telegram({token_source}, {count}个账号)\")\n\n if config[\"EMAIL_FROM\"] and config[\"EMAIL_PASSWORD\"] and config[\"EMAIL_TO\"]:\n from_source = \"环境变量\" if os.environ.get(\"EMAIL_FROM\") else \"配置文件\"\n notification_sources.append(f\"邮件({from_source})\")\n\n if config[\"NTFY_SERVER_URL\"] and config[\"NTFY_TOPIC\"]:\n topics = parse_multi_account_config(config[\"NTFY_TOPIC\"])\n tokens = parse_multi_account_config(config[\"NTFY_TOKEN\"])\n if tokens:\n valid, count = validate_paired_configs(\n {\"topic\": topics, \"token\": tokens},\n \"ntfy\"\n )\n if valid and count > 0:\n count = min(count, max_accounts)\n server_source = \"环境变量\" if os.environ.get(\"NTFY_SERVER_URL\") else \"配置文件\"\n notification_sources.append(f\"ntfy({server_source}, {count}个账号)\")\n else:\n count = min(len(topics), max_accounts)\n server_source = \"环境变量\" if os.environ.get(\"NTFY_SERVER_URL\") else \"配置文件\"\n notification_sources.append(f\"ntfy({server_source}, {count}个账号)\")\n\n if config[\"BARK_URL\"]:\n accounts = parse_multi_account_config(config[\"BARK_URL\"])\n count = min(len(accounts), max_accounts)\n bark_source = \"环境变量\" if os.environ.get(\"BARK_URL\") else \"配置文件\"\n notification_sources.append(f\"Bark({bark_source}, {count}个账号)\")\n\n if config[\"SLACK_WEBHOOK_URL\"]:\n accounts = parse_multi_account_config(config[\"SLACK_WEBHOOK_URL\"])\n count = min(len(accounts), max_accounts)\n slack_source = \"环境变量\" if os.environ.get(\"SLACK_WEBHOOK_URL\") else \"配置文件\"\n notification_sources.append(f\"Slack({slack_source}, {count}个账号)\")\n\n if notification_sources:\n print(f\"通知渠道配置来源: {', '.join(notification_sources)}\")\n print(f\"每个渠道最大账号数: {max_accounts}\")\n else:\n print(\"未配置任何通知渠道\")\n\n\ndef _load_llm_config(config_data: Dict) -> Dict:\n \"\"\"加载 LLM 配置\"\"\"\n llm_config = config_data.get(\"llm\", {})\n return {\n \"ENABLED\": _get_env_bool(\"LLM_ENABLED\") or llm_config.get(\"enabled\", False),\n \"PROVIDER\": _get_env_str(\"LLM_PROVIDER\") or llm_config.get(\"provider\", \"deepseek\"),\n \"API_KEY\": _get_env_str(\"LLM_API_KEY\") or llm_config.get(\"api_key\", \"\"),\n \"BASE_URL\": _get_env_str(\"LLM_BASE_URL\") or llm_config.get(\"base_url\", \"https://api.deepseek.com\"),\n \"MODEL\": _get_env_str(\"LLM_MODEL\") or llm_config.get(\"model\", \"deepseek-chat\"),\n }\n\n\ndef load_config(config_path: Optional[str] = None) -> Dict[str, Any]:\n \"\"\"\n 加载配置文件\n\n Args:\n config_path: 配置文件路径,默认从环境变量 CONFIG_PATH 获取或使用 config/config.yaml\n\n Returns:\n 包含所有配置的字典\n\n Raises:\n FileNotFoundError: 配置文件不存在\n \"\"\"\n if config_path is None:\n config_path = os.environ.get(\"CONFIG_PATH\", \"config/config.yaml\")\n\n if not Path(config_path).exists():\n raise FileNotFoundError(f\"配置文件 {config_path} 不存在\")\n\n with open(config_path, \"r\", encoding=\"utf-8\") as f:\n config_data = yaml.safe_load(f)\n\n print(f\"配置文件加载成功: {config_path}\")\n\n # 合并所有配置\n config = {}\n\n # 应用配置\n config.update(_load_app_config(config_data))\n\n # 爬虫配置\n config.update(_load_crawler_config(config_data))\n\n # 报告配置\n config.update(_load_report_config(config_data))\n\n # 通知配置\n config.update(_load_notification_config(config_data))\n\n # 推送窗口配置\n config[\"PUSH_WINDOW\"] = _load_push_window_config(config_data)\n\n # 权重配置\n config[\"WEIGHT_CONFIG\"] = _load_weight_config(config_data)\n\n # 平台配置\n config[\"PLATFORMS\"] = config_data.get(\"platforms\", [])\n\n # 存储配置\n config[\"STORAGE\"] = _load_storage_config(config_data)\n\n # LLM 配置\n config[\"LLM\"] = _load_llm_config(config_data)\n\n # Webhook 配置\n config.update(_load_webhook_config(config_data))\n\n # 打印通知渠道配置来源\n _print_notification_sources(config)\n\n return config\n"}, "1a752186955b6d3353a45f8f496466e8dab8fde9": {"t": 1785758581.7836063, "n": 13350, "h": "b02dabe3459be68f", "c": "# coding=utf-8\n\"\"\"\n配置加载模块\n\n负责从 YAML 配置文件和环境变量加载配置。\n\"\"\"\n\nimport os\nfrom pathlib import Path\nfrom typing import Dict, Any, Optional\n\nimport yaml\n\nfrom .config import parse_multi_account_config, validate_paired_configs\n\n\ndef _get_env_bool(key: str, default: bool = False) -> Optional[bool]:\n \"\"\"从环境变量获取布尔值,如果未设置返回 None\"\"\"\n value = os.environ.get(key, \"\").strip().lower()\n if not value:\n return None\n return value in (\"true\", \"1\")\n\n\ndef _get_env_int(key: str, default: int = 0) -> int:\n \"\"\"从环境变量获取整数值\"\"\"\n value = os.environ.get(key, \"\").strip()\n if not value:\n return default\n try:\n return int(value)\n except ValueError:\n return default\n\n\ndef _get_env_str(key: str, default: str = \"\") -> str:\n \"\"\"从环境变量获取字符串值\"\"\"\n return os.environ.get(key, \"\").strip() or default\n\n\ndef _load_app_config(config_data: Dict) -> Dict:\n \"\"\"加载应用配置\"\"\"\n app_config = config_data.get(\"app\", {})\n return {\n \"VERSION_CHECK_URL\": app_config.get(\"version_check_url\", \"\"),\n \"SHOW_VERSION_UPDATE\": app_config.get(\"show_version_update\", True),\n \"TIMEZONE\": _get_env_str(\"TIMEZONE\") or app_config.get(\"timezone\", \"Asia/Shanghai\"),\n }\n\n\ndef _load_crawler_config(config_data: Dict) -> Dict:\n \"\"\"加载爬虫配置\"\"\"\n crawler_config = config_data.get(\"crawler\", {})\n enable_crawler_env = _get_env_bool(\"ENABLE_CRAWLER\")\n return {\n \"REQUEST_INTERVAL\": crawler_config.get(\"request_interval\", 100),\n \"USE_PROXY\": crawler_config.get(\"use_proxy\", False),\n \"DEFAULT_PROXY\": crawler_config.get(\"default_proxy\", \"\"),\n \"ENABLE_CRAWLER\": enable_crawler_env if enable_crawler_env is not None else crawler_config.get(\"enable_crawler\", True),\n }\n\n\ndef _load_report_config(config_data: Dict) -> Dict:\n \"\"\"加载报告配置\"\"\"\n report_config = config_data.get(\"report\", {})\n\n # 环境变量覆盖\n sort_by_position_env = _get_env_bool(\"SORT_BY_POSITION_FIRST\")\n reverse_content_env = _get_env_bool(\"REVERSE_CONTENT_ORDER\")\n max_news_env = _get_env_int(\"MAX_NEWS_PER_KEYWORD\")\n\n return {\n \"REPORT_MODE\": _get_env_str(\"REPORT_MODE\") or report_config.get(\"mode\", \"daily\"),\n \"RANK_THRESHOLD\": report_config.get(\"rank_threshold\", 10),\n \"SORT_BY_POSITION_FIRST\": sort_by_position_env if sort_by_position_env is not None else report_config.get(\"sort_by_position_first\", False),\n \"MAX_NEWS_PER_KEYWORD\": max_news_env or report_config.get(\"max_news_per_keyword\", 0),\n \"REVERSE_CONTENT_ORDER\": reverse_content_env if reverse_content_env is not None else report_config.get(\"reverse_content_order\", False),\n }\n\n\ndef _load_notification_config(config_data: Dict) -> Dict:\n \"\"\"加载通知配置\"\"\"\n notification = config_data.get(\"notification\", {})\n enable_notification_env = _get_env_bool(\"ENABLE_NOTIFICATION\")\n\n return {\n \"ENABLE_NOTIFICATION\": enable_notification_env if enable_notification_env is not None else notification.get(\"enable_notification\", True),\n \"MESSAGE_BATCH_SIZE\": notification.get(\"message_batch_size\", 4000),\n \"DINGTALK_BATCH_SIZE\": notification.get(\"dingtalk_batch_size\", 20000),\n \"FEISHU_BATCH_SIZE\": notification.get(\"feishu_batch_size\", 29000),\n \"BARK_BATCH_SIZE\": notification.get(\"bark_batch_size\", 3600),\n \"SLACK_BATCH_SIZE\": notification.get(\"slack_batch_size\", 4000),\n \"BATCH_SEND_INTERVAL\": notification.get(\"batch_send_interval\", 1.0),\n \"FEISHU_MESSAGE_SEPARATOR\": notification.get(\"feishu_message_separator\", \"---\"),\n \"MAX_ACCOUNTS_PER_CHANNEL\": _get_env_int(\"MAX_ACCOUNTS_PER_CHANNEL\") or notification.get(\"max_accounts_per_channel\", 3),\n }\n\n\ndef _load_push_window_config(config_data: Dict) -> Dict:\n \"\"\"加载推送窗口配置\"\"\"\n notification = config_data.get(\"notification\", {})\n push_window = notification.get(\"push_window\", {})\n time_range = push_window.get(\"time_range\", {})\n\n enabled_env = _get_env_bool(\"PUSH_WINDOW_ENABLED\")\n once_per_day_env = _get_env_bool(\"PUSH_WINDOW_ONCE_PER_DAY\")\n\n return {\n \"ENABLED\": enabled_env if enabled_env is not None else push_window.get(\"enabled\", False),\n \"TIME_RANGE\": {\n \"START\": _get_env_str(\"PUSH_WINDOW_START\") or time_range.get(\"start\", \"08:00\"),\n \"END\": _get_env_str(\"PUSH_WINDOW_END\") or time_range.get(\"end\", \"22:00\"),\n },\n \"ONCE_PER_DAY\": once_per_day_env if once_per_day_env is not None else push_window.get(\"once_per_day\", True),\n }\n\n\ndef _load_weight_config(config_data: Dict) -> Dict:\n \"\"\"加载权重配置\"\"\"\n weight = config_data.get(\"weight\", {})\n return {\n \"RANK_WEIGHT\": weight.get(\"rank_weight\", 1.0),\n \"FREQUENCY_WEIGHT\": weight.get(\"frequency_weight\", 1.0),\n \"HOTNESS_WEIGHT\": weight.get(\"hotness_weight\", 1.0),\n }\n\n\ndef _load_storage_config(config_data: Dict) -> Dict:\n \"\"\"加载存储配置\"\"\"\n storage = config_data.get(\"storage\", {})\n formats = storage.get(\"formats\", {})\n local = storage.get(\"local\", {})\n remote = storage.get(\"remote\", {})\n pull = storage.get(\"pull\", {})\n\n txt_enabled_env = _get_env_bool(\"STORAGE_TXT_ENABLED\")\n html_enabled_env = _get_env_bool(\"STORAGE_HTML_ENABLED\")\n pull_enabled_env = _get_env_bool(\"PULL_ENABLED\")\n\n return {\n \"BACKEND\": _get_env_str(\"STORAGE_BACKEND\") or storage.get(\"backend\", \"auto\"),\n \"FORMATS\": {\n \"SQLITE\": formats.get(\"sqlite\", True),\n \"TXT\": txt_enabled_env if txt_enabled_env is not None else formats.get(\"txt\", True),\n \"HTML\": html_enabled_env if html_enabled_env is not None else formats.get(\"html\", True),\n },\n \"LOCAL\": {\n \"DATA_DIR\": local.get(\"data_dir\", \"output\"),\n \"RETENTION_DAYS\": _get_env_int(\"LOCAL_RETENTION_DAYS\") or local.get(\"retention_days\", 0),\n },\n \"REMOTE\": {\n \"ENDPOINT_URL\": _get_env_str(\"S3_ENDPOINT_URL\") or remote.get(\"endpoint_url\", \"\"),\n \"BUCKET_NAME\": _get_env_str(\"S3_BUCKET_NAME\") or remote.get(\"bucket_name\", \"\"),\n \"ACCESS_KEY_ID\": _get_env_str(\"S3_ACCESS_KEY_ID\") or remote.get(\"access_key_id\", \"\"),\n \"SECRET_ACCESS_KEY\": _get_env_str(\"S3_SECRET_ACCESS_KEY\") or remote.get(\"secret_access_key\", \"\"),\n \"REGION\": _get_env_str(\"S3_REGION\") or remote.get(\"region\", \"\"),\n \"RETENTION_DAYS\": _get_env_int(\"REMOTE_RETENTION_DAYS\") or remote.get(\"retention_days\", 0),\n },\n \"PULL\": {\n \"ENABLED\": pull_enabled_env if pull_enabled_env is not None else pull.get(\"enabled\", False),\n \"DAYS\": _get_env_int(\"PULL_DAYS\") or pull.get(\"days\", 7),\n },\n }\n\n\ndef _load_webhook_config(config_data: Dict) -> Dict:\n \"\"\"加载 Webhook 配置\"\"\"\n notification = config_data.get(\"notification\", {})\n webhooks = notification.get(\"webhooks\", {})\n\n return {\n # 飞书\n \"FEISHU_WEBHOOK_URL\": _get_env_str(\"FEISHU_WEBHOOK_URL\") or webhooks.get(\"feishu_url\", \"\"),\n # 钉钉\n \"DINGTALK_WEBHOOK_URL\": _get_env_str(\"DINGTALK_WEBHOOK_URL\") or webhooks.get(\"dingtalk_url\", \"\"),\n # 企业微信\n \"WEWORK_WEBHOOK_URL\": _get_env_str(\"WEWORK_WEBHOOK_URL\") or webhooks.get(\"wework_url\", \"\"),\n \"WEWORK_MSG_TYPE\": _get_env_str(\"WEWORK_MSG_TYPE\") or webhooks.get(\"wework_msg_type\", \"markdown\"),\n # Telegram\n \"TELEGRAM_BOT_TOKEN\": _get_env_str(\"TELEGRAM_BOT_TOKEN\") or webhooks.get(\"telegram_bot_token\", \"\"),\n \"TELEGRAM_CHAT_ID\": _get_env_str(\"TELEGRAM_CHAT_ID\") or webhooks.get(\"telegram_chat_id\", \"\"),\n # 邮件\n \"EMAIL_FROM\": _get_env_str(\"EMAIL_FROM\") or webhooks.get(\"email_from\", \"\"),\n \"EMAIL_PASSWORD\": _get_env_str(\"EMAIL_PASSWORD\") or webhooks.get(\"email_password\", \"\"),\n \"EMAIL_TO\": _get_env_str(\"EMAIL_TO\") or webhooks.get(\"email_to\", \"\"),\n \"EMAIL_SMTP_SERVER\": _get_env_str(\"EMAIL_SMTP_SERVER\") or webhooks.get(\"email_smtp_server\", \"\"),\n \"EMAIL_SMTP_PORT\": _get_env_str(\"EMAIL_SMTP_PORT\") or webhooks.get(\"email_smtp_port\", \"\"),\n # ntfy\n \"NTFY_SERVER_URL\": _get_env_str(\"NTFY_SERVER_URL\") or webhooks.get(\"ntfy_server_url\") or \"https://ntfy.sh\",\n \"NTFY_TOPIC\": _get_env_str(\"NTFY_TOPIC\") or webhooks.get(\"ntfy_topic\", \"\"),\n \"NTFY_TOKEN\": _get_env_str(\"NTFY_TOKEN\") or webhooks.get(\"ntfy_token\", \"\"),\n # Bark\n \"BARK_URL\": _get_env_str(\"BARK_URL\") or webhooks.get(\"bark_url\", \"\"),\n # Slack\n \"SLACK_WEBHOOK_URL\": _get_env_str(\"SLACK_WEBHOOK_URL\") or webhooks.get(\"slack_webhook_url\", \"\"),\n }\n\n\ndef _print_notification_sources(config: Dict) -> None:\n \"\"\"打印通知渠道配置来源信息\"\"\"\n notification_sources = []\n max_accounts = config[\"MAX_ACCOUNTS_PER_CHANNEL\"]\n\n if config[\"FEISHU_WEBHOOK_URL\"]:\n accounts = parse_multi_account_config(config[\"FEISHU_WEBHOOK_URL\"])\n count = min(len(accounts), max_accounts)\n source = \"环境变量\" if os.environ.get(\"FEISHU_WEBHOOK_URL\") else \"配置文件\"\n notification_sources.append(f\"飞书({source}, {count}个账号)\")\n\n if config[\"DINGTALK_WEBHOOK_URL\"]:\n accounts = parse_multi_account_config(config[\"DINGTALK_WEBHOOK_URL\"])\n count = min(len(accounts), max_accounts)\n source = \"环境变量\" if os.environ.get(\"DINGTALK_WEBHOOK_URL\") else \"配置文件\"\n notification_sources.append(f\"钉钉({source}, {count}个账号)\")\n\n if config[\"WEWORK_WEBHOOK_URL\"]:\n accounts = parse_multi_account_config(config[\"WEWORK_WEBHOOK_URL\"])\n count = min(len(accounts), max_accounts)\n source = \"环境变量\" if os.environ.get(\"WEWORK_WEBHOOK_URL\") else \"配置文件\"\n notification_sources.append(f\"企业微信({source}, {count}个账号)\")\n\n if config[\"TELEGRAM_BOT_TOKEN\"] and config[\"TELEGRAM_CHAT_ID\"]:\n tokens = parse_multi_account_config(config[\"TELEGRAM_BOT_TOKEN\"])\n chat_ids = parse_multi_account_config(config[\"TELEGRAM_CHAT_ID\"])\n valid, count = validate_paired_configs(\n {\"bot_token\": tokens, \"chat_id\": chat_ids},\n \"Telegram\",\n required_keys=[\"bot_token\", \"chat_id\"]\n )\n if valid and count > 0:\n count = min(count, max_accounts)\n token_source = \"环境变量\" if os.environ.get(\"TELEGRAM_BOT_TOKEN\") else \"配置文件\"\n notification_sources.append(f\"Telegram({token_source}, {count}个账号)\")\n\n if config[\"EMAIL_FROM\"] and config[\"EMAIL_PASSWORD\"] and config[\"EMAIL_TO\"]:\n from_source = \"环境变量\" if os.environ.get(\"EMAIL_FROM\") else \"配置文件\"\n notification_sources.append(f\"邮件({from_source})\")\n\n if config[\"NTFY_SERVER_URL\"] and config[\"NTFY_TOPIC\"]:\n topics = parse_multi_account_config(config[\"NTFY_TOPIC\"])\n tokens = parse_multi_account_config(config[\"NTFY_TOKEN\"])\n if tokens:\n valid, count = validate_paired_configs(\n {\"topic\": topics, \"token\": tokens},\n \"ntfy\"\n )\n if valid and count > 0:\n count = min(count, max_accounts)\n server_source = \"环境变量\" if os.environ.get(\"NTFY_SERVER_URL\") else \"配置文件\"\n notification_sources.append(f\"ntfy({server_source}, {count}个账号)\")\n else:\n count = min(len(topics), max_accounts)\n server_source = \"环境变量\" if os.environ.get(\"NTFY_SERVER_URL\") else \"配置文件\"\n notification_sources.append(f\"ntfy({server_source}, {count}个账号)\")\n\n if config[\"BARK_URL\"]:\n accounts = parse_multi_account_config(config[\"BARK_URL\"])\n count = min(len(accounts), max_accounts)\n bark_source = \"环境变量\" if os.environ.get(\"BARK_URL\") else \"配置文件\"\n notification_sources.append(f\"Bark({bark_source}, {count}个账号)\")\n\n if config[\"SLACK_WEBHOOK_URL\"]:\n accounts = parse_multi_account_config(config[\"SLACK_WEBHOOK_URL\"])\n count = min(len(accounts), max_accounts)\n slack_source = \"环境变量\" if os.environ.get(\"SLACK_WEBHOOK_URL\") else \"配置文件\"\n notification_sources.append(f\"Slack({slack_source}, {count}个账号)\")\n\n if notification_sources:\n print(f\"通知渠道配置来源: {', '.join(notification_sources)}\")\n print(f\"每个渠道最大账号数: {max_accounts}\")\n else:\n print(\"未配置任何通知渠道\")\n\n\ndef load_config(config_path: Optional[str] = None) -> Dict[str, Any]:\n \"\"\"\n 加载配置文件\n\n Args:\n config_path: 配置文件路径,默认从环境变量 CONFIG_PATH 获取或使用 config/config.yaml\n\n Returns:\n 包含所有配置的字典\n\n Raises:\n FileNotFoundError: 配置文件不存在\n \"\"\"\n if config_path is None:\n config_path = os.environ.get(\"CONFIG_PATH\", \"config/config.yaml\")\n\n if not Path(config_path).exists():\n raise FileNotFoundError(f\"配置文件 {config_path} 不存在\")\n\n with open(config_path, \"r\", encoding=\"utf-8\") as f:\n config_data = yaml.safe_load(f)\n\n print(f\"配置文件加载成功: {config_path}\")\n\n # 合并所有配置\n config = {}\n\n # 应用配置\n config.update(_load_app_config(config_data))\n\n # 爬虫配置\n config.update(_load_crawler_config(config_data))\n\n # 报告配置\n config.update(_load_report_config(config_data))\n\n # 通知配置\n config.update(_load_notification_config(config_data))\n\n # 推送窗口配置\n config[\"PUSH_WINDOW\"] = _load_push_window_config(config_data)\n\n # 权重配置\n config[\"WEIGHT_CONFIG\"] = _load_weight_config(config_data)\n\n # 平台配置\n config[\"PLATFORMS\"] = config_data.get(\"platforms\", [])\n\n # 存储配置\n config[\"STORAGE\"] = _load_storage_config(config_data)\n\n # Webhook 配置\n config.update(_load_webhook_config(config_data))\n\n # 打印通知渠道配置来源\n _print_notification_sources(config)\n\n return config\n"}}}}