sansan пре 7 месеци
родитељ
комит
89ab87cad2
8 измењених фајлова са 371 додато и 30 уклоњено
  1. 5 0
      .github/workflows/crawler.yml
  2. 5 0
      config/config.yaml
  3. 5 0
      docker/.env
  4. 5 0
      docker/docker-compose-build.yml
  5. 5 0
      docker/docker-compose.yml
  6. 258 6
      main.py
  7. 87 23
      readme.md
  8. 1 1
      version

+ 5 - 0
.github/workflows/crawler.yml

@@ -53,6 +53,11 @@ jobs:
           TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
           DINGTALK_WEBHOOK_URL: ${{ secrets.DINGTALK_WEBHOOK_URL }}
           WEWORK_WEBHOOK_URL: ${{ secrets.WEWORK_WEBHOOK_URL }}
+          EMAIL_FROM: ${{ secrets.EMAIL_FROM }}
+          EMAIL_PASSWORD: ${{ secrets.EMAIL_PASSWORD }}
+          EMAIL_TO: ${{ secrets.EMAIL_TO }}
+          EMAIL_SMTP_SERVER: ${{ secrets.EMAIL_SMTP_SERVER }}
+          EMAIL_SMTP_PORT: ${{ secrets.EMAIL_SMTP_PORT }}
           GITHUB_ACTIONS: true
         run: python main.py
 

+ 5 - 0
config/config.yaml

@@ -53,6 +53,11 @@ notification:
     wework_url: "" # 企业微信机器人的 webhook URL
     telegram_bot_token: "" # Telegram Bot Token
     telegram_chat_id: "" # Telegram Chat ID
+    email_from: "" # 发件人邮箱地址
+    email_password: "" # 发件人邮箱密码或授权码
+    email_to: "" # 收件人邮箱地址,多个收件人用逗号分隔
+    email_smtp_server: "" # SMTP服务器地址(可选,留空自动识别)
+    email_smtp_port: "" # SMTP端口(可选,留空自动识别)
 
 # 用于让关注度更高的新闻在更前面显示,即用算法重新组合不同平台的热搜排序形成你侧重的热搜,合起来是 1 就行
 weight:

+ 5 - 0
docker/.env

@@ -4,6 +4,11 @@ TELEGRAM_BOT_TOKEN=
 TELEGRAM_CHAT_ID=
 DINGTALK_WEBHOOK_URL=
 WEWORK_WEBHOOK_URL=
+EMAIL_FROM=
+EMAIL_PASSWORD=
+EMAIL_TO=
+EMAIL_SMTP_SERVER=
+EMAIL_SMTP_PORT=
 
 # 运行配置
 CRON_SCHEDULE=*/30 * * * * # 定时任务表达式,每 30 分钟执行一次(比如 8点,8点半,9点,9点半这种时间规律执行)

+ 5 - 0
docker/docker-compose-build.yml

@@ -17,6 +17,11 @@ services:
       - TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID:-}
       - DINGTALK_WEBHOOK_URL=${DINGTALK_WEBHOOK_URL:-}
       - WEWORK_WEBHOOK_URL=${WEWORK_WEBHOOK_URL:-}
+      - EMAIL_FROM=${EMAIL_FROM:-}
+      - EMAIL_PASSWORD=${EMAIL_PASSWORD:-}
+      - EMAIL_TO=${EMAIL_TO:-}
+      - EMAIL_SMTP_SERVER=${EMAIL_SMTP_SERVER:-}
+      - EMAIL_SMTP_PORT=${EMAIL_SMTP_PORT:-}
       - CRON_SCHEDULE=${CRON_SCHEDULE:-*/5 * * * *}
       - RUN_MODE=${RUN_MODE:-cron}
       - IMMEDIATE_RUN=${IMMEDIATE_RUN:-true}

+ 5 - 0
docker/docker-compose.yml

@@ -15,6 +15,11 @@ services:
       - TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID:-}
       - DINGTALK_WEBHOOK_URL=${DINGTALK_WEBHOOK_URL:-}
       - WEWORK_WEBHOOK_URL=${WEWORK_WEBHOOK_URL:-}
+      - EMAIL_FROM=${EMAIL_FROM:-}
+      - EMAIL_PASSWORD=${EMAIL_PASSWORD:-}
+      - EMAIL_TO=${EMAIL_TO:-}
+      - EMAIL_SMTP_SERVER=${EMAIL_SMTP_SERVER:-}
+      - EMAIL_SMTP_PORT=${EMAIL_SMTP_PORT:-}
       - CRON_SCHEDULE=${CRON_SCHEDULE:-*/5 * * * *}
       - RUN_MODE=${RUN_MODE:-cron}
       - IMMEDIATE_RUN=${IMMEDIATE_RUN:-true}

+ 258 - 6
main.py

@@ -6,6 +6,11 @@ import random
 import re
 import time
 import webbrowser
+import smtplib
+from email.mime.text import MIMEText
+from email.mime.multipart import MIMEMultipart
+from email.header import Header
+from email.utils import formataddr, formatdate, make_msgid
 from datetime import datetime
 from pathlib import Path
 from typing import Dict, List, Tuple, Optional, Union
@@ -15,9 +20,69 @@ import requests
 import yaml
 
 
-VERSION = "2.2.0"
+VERSION = "2.3.0"
 
 
+# === SMTP邮件配置 ===
+SMTP_CONFIGS = {
+    # Gmail
+    'gmail.com': {
+        'server': 'smtp.gmail.com',
+        'port': 587,
+        'encryption': 'TLS'
+    },
+    
+    # QQ邮箱
+    'qq.com': {
+        'server': 'smtp.qq.com', 
+        'port': 587,
+        'encryption': 'TLS'
+    },
+    
+    # Outlook
+    'outlook.com': {
+        'server': 'smtp-mail.outlook.com',
+        'port': 587,
+        'encryption': 'TLS'
+    },
+    'hotmail.com': {
+        'server': 'smtp-mail.outlook.com',
+        'port': 587,
+        'encryption': 'TLS'
+    },
+    'live.com': {
+        'server': 'smtp-mail.outlook.com',
+        'port': 587,
+        'encryption': 'TLS'
+    },
+    
+    # 网易邮箱
+    '163.com': {
+        'server': 'smtp.163.com',
+        'port': 587,
+        'encryption': 'TLS'
+    },
+    '126.com': {
+        'server': 'smtp.126.com',
+        'port': 587,
+        'encryption': 'TLS'
+    },
+    
+    # 新浪邮箱
+    'sina.com': {
+        'server': 'smtp.sina.com',
+        'port': 587,
+        'encryption': 'TLS'
+    },
+    
+    # 搜狐邮箱
+    'sohu.com': {
+        'server': 'smtp.sohu.com',
+        'port': 587,
+        'encryption': 'TLS'
+    }
+}
+
 # === 配置管理 ===
 def load_config():
     """加载配置文件"""
@@ -96,6 +161,23 @@ def load_config():
     config["TELEGRAM_CHAT_ID"] = os.environ.get(
         "TELEGRAM_CHAT_ID", ""
     ).strip() or webhooks.get("telegram_chat_id", "")
+    
+    # 邮件配置
+    config["EMAIL_FROM"] = os.environ.get(
+        "EMAIL_FROM", ""
+    ).strip() or webhooks.get("email_from", "")
+    config["EMAIL_PASSWORD"] = os.environ.get(
+        "EMAIL_PASSWORD", ""
+    ).strip() or webhooks.get("email_password", "")
+    config["EMAIL_TO"] = os.environ.get(
+        "EMAIL_TO", ""
+    ).strip() or webhooks.get("email_to", "")
+    config["EMAIL_SMTP_SERVER"] = os.environ.get(
+        "EMAIL_SMTP_SERVER", ""
+    ).strip() or webhooks.get("email_smtp_server", "")
+    config["EMAIL_SMTP_PORT"] = os.environ.get(
+        "EMAIL_SMTP_PORT", ""
+    ).strip() or webhooks.get("email_smtp_port", "")
 
     # 输出配置来源信息
     webhook_sources = []
@@ -114,7 +196,10 @@ def load_config():
         )
         chat_source = "环境变量" if os.environ.get("TELEGRAM_CHAT_ID") else "配置文件"
         webhook_sources.append(f"Telegram({token_source}/{chat_source})")
-
+    if config["EMAIL_FROM"] and config["EMAIL_PASSWORD"] and config["EMAIL_TO"]:
+        from_source = "环境变量" if os.environ.get("EMAIL_FROM") else "配置文件"
+        webhook_sources.append(f"邮件({from_source})")
+        
     if webhook_sources:
         print(f"Webhook 配置来源: {', '.join(webhook_sources)}")
     else:
@@ -1464,6 +1549,7 @@ def generate_html_report(
     id_to_name: Optional[Dict] = None,
     mode: str = "daily",
     is_daily_summary: bool = False,
+    update_info: Optional[Dict] = None,
 ) -> str:
     """生成HTML报告"""
     if is_daily_summary:
@@ -1481,7 +1567,7 @@ def generate_html_report(
     report_data = prepare_report_data(stats, failed_ids, new_titles, id_to_name, mode)
 
     html_content = render_html_content(
-        report_data, total_titles, is_daily_summary, mode
+        report_data, total_titles, is_daily_summary, mode, update_info
     )
 
     with open(file_path, "w", encoding="utf-8") as f:
@@ -1500,6 +1586,7 @@ def render_html_content(
     total_titles: int,
     is_daily_summary: bool = False,
     mode: str = "daily",
+    update_info: Optional[Dict] = None,
 ) -> str:
     """渲染HTML内容"""
     html = """
@@ -1876,7 +1963,7 @@ def render_html_content(
             .footer-content {
                 font-size: 13px;
                 color: #6b7280;
-                line-height: 1.4;
+                line-height: 1.6;
             }
             
             .footer-link {
@@ -2157,7 +2244,16 @@ def render_html_content(
                     由 <span class="project-name">TrendRadar</span> 生成 · 
                     <a href="https://github.com/sansan0/TrendRadar" target="_blank" class="footer-link">
                         GitHub 开源项目
-                    </a>
+                    </a>"""
+                    
+    if update_info:
+        html += f"""
+                    <br>
+                    <span style="color: #ea580c; font-weight: 500;">
+                        发现新版本 {update_info['remote_version']},当前版本 {update_info['current_version']}
+                    </span>"""
+
+    html += """
                 </div>
             </div>
         </div>
@@ -2823,6 +2919,7 @@ def send_to_webhooks(
     update_info: Optional[Dict] = None,
     proxy_url: Optional[str] = None,
     mode: str = "daily",
+    html_file_path: Optional[str] = None,
 ) -> Dict[str, bool]:
     """发送数据到多个webhook平台"""
     results = {}
@@ -2851,6 +2948,11 @@ def send_to_webhooks(
     wework_url = CONFIG["WEWORK_WEBHOOK_URL"]
     telegram_token = CONFIG["TELEGRAM_BOT_TOKEN"]
     telegram_chat_id = CONFIG["TELEGRAM_CHAT_ID"]
+    email_from = CONFIG["EMAIL_FROM"]
+    email_password = CONFIG["EMAIL_PASSWORD"]
+    email_to = CONFIG["EMAIL_TO"]
+    email_smtp_server = CONFIG.get("EMAIL_SMTP_SERVER", "")
+    email_smtp_port = CONFIG.get("EMAIL_SMTP_PORT", "")
 
     update_info_to_send = update_info if CONFIG["SHOW_VERSION_UPDATE"] else None
 
@@ -2884,6 +2986,18 @@ def send_to_webhooks(
             mode,
         )
 
+    # 发送邮件
+    if email_from and email_password and email_to:
+        results["email"] = send_to_email(
+            email_from,
+            email_password,
+            email_to,
+            report_type,
+            html_file_path,
+            email_smtp_server,
+            email_smtp_port,
+        )
+
     if not results:
         print("未配置任何webhook URL,跳过通知发送")
 
@@ -3156,6 +3270,137 @@ def send_to_telegram(
     print(f"Telegram所有 {len(batches)} 批次发送完成 [{report_type}]")
     return True
 
+def send_to_email(
+    from_email: str,
+    password: str,
+    to_email: str,
+    report_type: str,
+    html_file_path: str,
+    custom_smtp_server: Optional[str] = None,
+    custom_smtp_port: Optional[int] = None,
+) -> bool:
+    """发送邮件通知"""
+    try:
+        if not html_file_path or not Path(html_file_path).exists():
+            print(f"错误:HTML文件不存在或未提供: {html_file_path}")
+            return False
+            
+        print(f"使用HTML文件: {html_file_path}")
+        with open(html_file_path, "r", encoding="utf-8") as f:
+            html_content = f.read()
+        
+        domain = from_email.split('@')[-1].lower()
+        
+        if custom_smtp_server and custom_smtp_port:
+            # 使用自定义 SMTP 配置
+            smtp_server = custom_smtp_server
+            smtp_port = int(custom_smtp_port)
+            use_tls = smtp_port == 587
+        elif domain in SMTP_CONFIGS:
+            # 使用预设配置
+            config = SMTP_CONFIGS[domain]
+            smtp_server = config['server']
+            smtp_port = config['port']
+            use_tls = config['encryption'] == 'TLS'
+        else:
+            print(f"未识别的邮箱服务商: {domain},使用通用 SMTP 配置")
+            smtp_server = f"smtp.{domain}"
+            smtp_port = 587
+            use_tls = True
+        
+        msg = MIMEMultipart('alternative')
+        
+        # 严格按照 RFC 标准设置 From header
+        sender_name = "TrendRadar"
+        msg['From'] = formataddr((sender_name, from_email))
+        
+        # 设置收件人
+        recipients = [addr.strip() for addr in to_email.split(',')]
+        if len(recipients) == 1:
+            msg['To'] = recipients[0]
+        else:
+            msg['To'] = ', '.join(recipients)
+        
+        # 设置邮件主题
+        now = get_beijing_time()
+        subject = f"TrendRadar 热点分析报告 - {report_type} - {now.strftime('%m月%d日 %H:%M')}"
+        msg['Subject'] = Header(subject, 'utf-8')
+        
+        # 设置其他标准 header
+        msg['MIME-Version'] = '1.0'
+        msg['Date'] = formatdate(localtime=True)
+        msg['Message-ID'] = make_msgid()
+        
+        # 添加纯文本部分(作为备选)
+        text_content = f"""
+TrendRadar 热点分析报告
+========================
+报告类型:{report_type}
+生成时间:{now.strftime('%Y-%m-%d %H:%M:%S')}
+
+请使用支持HTML的邮件客户端查看完整报告内容。
+        """
+        text_part = MIMEText(text_content, 'plain', 'utf-8')
+        msg.attach(text_part)
+        
+        html_part = MIMEText(html_content, 'html', 'utf-8')
+        msg.attach(html_part)
+        
+        print(f"正在发送邮件到 {to_email}...")
+        print(f"SMTP 服务器: {smtp_server}:{smtp_port}")
+        print(f"发件人: {from_email}")
+        
+        try:
+            if use_tls:
+                # TLS 模式
+                server = smtplib.SMTP(smtp_server, smtp_port, timeout=30)
+                server.set_debuglevel(0)  # 设为1可以查看详细调试信息
+                server.ehlo()
+                server.starttls()
+                server.ehlo()
+            else:
+                # SSL 模式
+                server = smtplib.SMTP_SSL(smtp_server, smtp_port, timeout=30)
+                server.set_debuglevel(0)
+                server.ehlo()
+            
+            # 登录
+            server.login(from_email, password)
+            
+            # 发送邮件
+            server.send_message(msg)
+            server.quit()
+            
+            print(f"邮件发送成功 [{report_type}] -> {to_email}")
+            return True
+            
+        except smtplib.SMTPServerDisconnected:
+            print(f"邮件发送失败:服务器意外断开连接,请检查网络或稍后重试")
+            return False
+            
+    except smtplib.SMTPAuthenticationError as e:
+        print(f"邮件发送失败:认证错误,请检查邮箱和密码/授权码")
+        print(f"详细错误: {str(e)}")
+        return False
+    except smtplib.SMTPRecipientsRefused as e:
+        print(f"邮件发送失败:收件人地址被拒绝 {e}")
+        return False
+    except smtplib.SMTPSenderRefused as e:
+        print(f"邮件发送失败:发件人地址被拒绝 {e}")
+        return False
+    except smtplib.SMTPDataError as e:
+        print(f"邮件发送失败:邮件数据错误 {e}")
+        return False
+    except smtplib.SMTPConnectError as e:
+        print(f"邮件发送失败:无法连接到 SMTP 服务器 {smtp_server}:{smtp_port}")
+        print(f"详细错误: {str(e)}")
+        return False
+    except Exception as e:
+        print(f"邮件发送失败 [{report_type}]:{e}")
+        import traceback
+        traceback.print_exc()
+        return False
+
 
 # === 主分析器 ===
 class NewsAnalyzer:
@@ -3374,6 +3619,7 @@ class NewsAnalyzer:
             id_to_name=id_to_name,
             mode=mode,
             is_daily_summary=is_daily_summary,
+            update_info=self.update_info if CONFIG["SHOW_VERSION_UPDATE"] else None,
         )
 
         return stats, html_file
@@ -3386,6 +3632,7 @@ class NewsAnalyzer:
         failed_ids: Optional[List] = None,
         new_titles: Optional[Dict] = None,
         id_to_name: Optional[Dict] = None,
+        html_file_path: Optional[str] = None,
     ) -> bool:
         """统一的通知发送逻辑,包含所有判断条件"""
         has_webhook = self._has_webhook_configured()
@@ -3404,6 +3651,7 @@ class NewsAnalyzer:
                 self.update_info,
                 self.proxy_url,
                 mode=mode,
+                html_file_path=html_file_path, 
             )
             return True
         elif CONFIG["ENABLE_NOTIFICATION"] and not has_webhook:
@@ -3456,14 +3704,16 @@ class NewsAnalyzer:
         )
 
         print(f"{summary_type}报告已生成: {html_file}")
-
+        
         # 发送通知
         self._send_notification_if_needed(
             stats,
             mode_strategy["summary_report_type"],
             mode_strategy["summary_mode"],
+            failed_ids=[],
             new_titles=new_titles,
             id_to_name=id_to_name,
+            html_file_path=html_file, 
         )
 
         return html_file
@@ -3596,6 +3846,7 @@ class NewsAnalyzer:
                         failed_ids=failed_ids,
                         new_titles=historical_new_titles,
                         id_to_name=combined_id_to_name,
+                        html_file_path=html_file,
                     )
             else:
                 print("❌ 严重错误:无法读取刚保存的数据文件")
@@ -3624,6 +3875,7 @@ class NewsAnalyzer:
                     failed_ids=failed_ids,
                     new_titles=new_titles,
                     id_to_name=id_to_name,
+                    html_file_path=html_file,
                 )
 
         # 生成汇总报告(如果需要)

+ 87 - 23
readme.md

@@ -9,12 +9,13 @@
 [![GitHub Stars](https://img.shields.io/github/stars/sansan0/TrendRadar?style=flat-square&logo=github&color=yellow)](https://github.com/sansan0/TrendRadar/stargazers)
 [![GitHub Forks](https://img.shields.io/github/forks/sansan0/TrendRadar?style=flat-square&logo=github&color=blue)](https://github.com/sansan0/TrendRadar/network/members)
 [![License](https://img.shields.io/badge/license-GPL--3.0-blue.svg?style=flat-square)](LICENSE)
-[![Version](https://img.shields.io/badge/version-v2.2.0-green.svg?style=flat-square)](https://github.com/sansan0/TrendRadar)
+[![Version](https://img.shields.io/badge/version-v2.3.0-green.svg?style=flat-square)](https://github.com/sansan0/TrendRadar)
 
-[![企业微信通知](https://img.shields.io/badge/企业微信-通知支持-00D4AA?style=flat-square)](https://work.weixin.qq.com/)
-[![Telegram通知](https://img.shields.io/badge/Telegram-通知支持-00D4AA?style=flat-square)](https://telegram.org/)
-[![dingtalk通知](https://img.shields.io/badge/钉钉-通知支持-00D4AA?style=flat-square)](#)
-[![飞书通知](https://img.shields.io/badge/飞书-通知支持-00D4AA?style=flat-square)](https://www.feishu.cn/)
+[![企业微信通知](https://img.shields.io/badge/企业微信-通知-00D4AA?style=flat-square)](https://work.weixin.qq.com/)
+[![Telegram通知](https://img.shields.io/badge/Telegram-通知-00D4AA?style=flat-square)](https://telegram.org/)
+[![dingtalk通知](https://img.shields.io/badge/钉钉-通知-00D4AA?style=flat-square)](#)
+[![飞书通知](https://img.shields.io/badge/飞书-通知-00D4AA?style=flat-square)](https://www.feishu.cn/)
+[![邮件通知](https://img.shields.io/badge/Email-通知-00D4AA?style=flat-square)](mailto:) 
 [![GitHub Actions](https://img.shields.io/badge/GitHub_Actions-自动化-2088FF?style=flat-square&logo=github-actions&logoColor=white)](https://github.com/sansan0/TrendRadar)
 [![GitHub Pages](https://img.shields.io/badge/GitHub_Pages-部署-4285F4?style=flat-square&logo=github&logoColor=white)](https://sansan0.github.io/TrendRadar)
 [![Docker](https://img.shields.io/badge/Docker-部署-2496ED?style=flat-square&logo=docker&logoColor=white)](https://hub.docker.com/)
@@ -29,7 +30,7 @@
 - 遇到问题可选择以上 2 种方式获得帮助,[点此跳转到两者的区别](#问题答疑与1元点赞)
 
 <details>
-<summary>👉 点击查看<strong>致谢名单</strong> (当前 <strong>🔥22🔥</strong> 位)</summary>
+<summary>👉 点击查看<strong>致谢名单</strong> (当前 <strong>🔥23🔥</strong> 位)</summary>
 
 ### 数据支持
 
@@ -49,6 +50,7 @@
 
 |           点赞人            |  金额  |  日期  |             备注             |
 | :-------------------------: | :----: | :----: | :-----------------------: |
+|           *🍍           |  10  | 2025.9.21  |           |
 |           E*f           |  1  | 2025.9.20  |           |
 |           *记            |  1  | 2025.9.20  |           |
 |           z*u            |  2  | 2025.9.19  |           |
@@ -186,7 +188,11 @@ weight:
 
 ### **多渠道实时推送**
 
-支持**企业微信**(微信方案)、**飞书**、**钉钉**、**Telegram**,消息直达手机
+支持**企业微信**(+ 微信推送方案)、**飞书**、**钉钉**、**Telegram**、**邮件**,消息直达手机和邮箱
+
+- **邮件推送**:支持 QQ邮箱、Gmail、Outlook、163邮箱等主流邮箱服务
+- **智能识别**:自动识别邮箱服务商,也可以手动配置 SMTP 服务器
+- **HTML 格式**:精美的 HTML 邮件格式,与网页版效果一致
 
 ### **多端适配**
 - **GitHub Pages**:自动生成精美网页报告,PC/移动端适配
@@ -212,7 +218,7 @@ GitHub 一键 Fork 即可使用,无需编程基础。
 **典型场景:** 股市投资监控、品牌舆情追踪、行业动态关注、生活资讯获取
 
 
-| Github Pages 网页效果(手机端适配) | 飞书推送效果 |
+| Github Pages 效果(手机端适配、邮箱推送效果) | 飞书推送效果 |
 |:---:|:---:|
 | ![Github Pages效果](_image/github-pages.png) | ![飞书推送效果](_image/feishu.jpg) |
 
@@ -290,6 +296,28 @@ GitHub 一键 Fork 即可使用,无需编程基础。
 > 
 > 下一次**新功能**,大概会是 ai 分析功能(●'◡'●)
 
+### 2025/09/22 - v2.3.0
+
+- **新增邮件推送功能**,支持将热点新闻报告发送到邮箱
+- **智能 SMTP 识别**:自动识别 Gmail、QQ邮箱、Outlook、网易邮箱等 10+ 种邮箱服务商配置
+- **HTML 精美格式**:邮件内容采用与网页版相同的 HTML 格式,排版精美,移动端适配
+- **批量发送支持**:支持多个收件人,用逗号分隔即可同时发送给多人
+- **自定义 SMTP**:可自定义 SMTP 服务器和端口
+
+**使用说明**:
+- 适用场景:适合需要邮件归档、团队分享、定时报告的用户
+- 注意事项:为防止邮件群发功能被**滥用**,当前的群发是所有收件人都能看到彼此的邮箱地址,适合熟人间交流资讯
+
+**支持的邮箱服务**:
+- Gmail、QQ邮箱、Outlook/Hotmail、163/126邮箱、新浪邮箱、搜狐邮箱等
+
+**更新提示**:
+- 此次更新的内容比较多,建议删除原有 fork, 重新 fork 并配置
+
+
+<details>
+<summary><strong>👉 历史更新</strong></summary>
+
 ### 2025/09/17 - v2.2.0
 
 - 新增一键保存新闻图片功能,让你轻松分享关注的热点
@@ -300,9 +328,6 @@ GitHub 一键 Fork 即可使用,无需编程基础。
 - 实际效果:系统会自动将当前的新闻报告制作成一张精美图片,保存到你的手机相册或电脑桌面
 - 分享便利:你可以直接把这张图片发给朋友、发到朋友圈,或分享到工作群,让别人也能看到你发现的重要资讯
 
-<details>
-<summary><strong>👉 历史更新</strong></summary>
-
 ### 2025/09/13 - v2.1.2
 
 - 解决钉钉的推送容量限制导致的新闻推送失败问题(采用分批推送)
@@ -581,6 +606,49 @@ frequency_words.txt 文件增加了一个【必须词】功能,使用 + 号
       - `TELEGRAM_CHAT_ID`:填入第 2 步获得的 Chat ID
    </details>
 
+   <details>
+   <summary> <strong>👉 邮件推送</strong>(支持所有主流邮箱)</summary>
+   <br>
+
+   - 注意事项:为防止邮件群发功能被**滥用**,当前的群发是所有收件人都能看到彼此的邮箱地址,适合熟人间交流资讯。
+   - 仅供参考:请根据实际情况调整,邮箱方面并没有一一验证,是按照 SMTP 的标准配置的 
+
+   **GitHub Secret 配置:**
+   - 名称:`EMAIL_FROM` - 发件人邮箱地址
+   - 名称:`EMAIL_PASSWORD` - 邮箱密码或授权码
+   - 名称:`EMAIL_TO` - 收件人邮箱地址(多个收件人用英文逗号分隔)
+   - 名称:`EMAIL_SMTP_SERVER` - SMTP服务器地址(可选,留空则自动识别)
+   - 名称:`EMAIL_SMTP_PORT` - SMTP端口(可选,留空则自动识别)
+
+   **常见邮箱设置:**
+
+   #### QQ邮箱:
+   1. 登录 QQ邮箱网页版 → 设置 → 账户
+   2. 开启 POP3/SMTP 服务
+   3. 生成授权码(16位字母)
+   4. `EMAIL_PASSWORD` 填写授权码,而非 QQ 密码
+
+   #### Gmail:
+   1. 开启两步验证
+   2. 生成应用专用密码
+   3. `EMAIL_PASSWORD` 填写应用专用密码
+
+   #### 163/126邮箱:
+   1. 登录网页版 → 设置 → POP3/SMTP/IMAP
+   2. 开启 SMTP 服务
+   3. 设置客户端授权码
+   4. `EMAIL_PASSWORD` 填写授权码
+
+   **高级配置**:
+   如果自动识别失败,可手动配置 SMTP:
+   - `EMAIL_SMTP_SERVER`:如 smtp.gmail.com
+   - `EMAIL_SMTP_PORT`:如 587(TLS)或 465(SSL)
+
+   **多收件人设置**:
+   - EMAIL_TO="user1@example.com,user2@example.com,user3@example.com"
+
+   </details>
+
 3. **主要配置**:
 
     - **推送设置:** : 在 [config/config.yaml](config/config.yaml) 中进行,可根据里面的描述文字操作,这里不重复了
@@ -765,7 +833,7 @@ docker run -d --name trend-radar \
   -e IMMEDIATE_RUN="true" \
   wantcat/trendradar:latest
 
-# 或者启用手机应用推送通知
+# 或者启用手机应用推送通知或邮件通知
 docker run -d --name trend-radar \
   -v ./config:/app/config:ro \
   -v ./output:/app/output \
@@ -774,6 +842,9 @@ docker run -d --name trend-radar \
   -e WEWORK_WEBHOOK_URL="你的企业微信webhook" \
   -e TELEGRAM_BOT_TOKEN="你的telegram_bot_token" \
   -e TELEGRAM_CHAT_ID="你的telegram_chat_id" \
+  -e EMAIL_FROM="你的发件邮箱" \
+  -e EMAIL_PASSWORD="你的邮箱密码或授权码" \
+  -e EMAIL_TO="收件人邮箱" \
   -e CRON_SCHEDULE="*/30 * * * *" \
   -e RUN_MODE="cron" \
   -e IMMEDIATE_RUN="true" \
@@ -946,21 +1017,14 @@ docker exec -it trend-radar ls -la /app/config/
 
 > 心意到就行,收到的**点赞**用于提高开发者开源的积极性。你们的**点赞**已记录于最顶部的【致谢名单】
 
-<div align="center">
-
 |公众号关注 |微信点赞 | 支付宝点赞 |
 |:---:|:---:|:---:| 
 | <img src="_image/weixin.png" width="300" title="硅基茶水间"/> | <img src="https://cdn-1258574687.cos.ap-shanghai.myqcloud.com/img/%2F2025%2F07%2F17%2F2ae0a88d98079f7e876c2b4dc85233c6-9e8025.JPG" width="300" title="微信支付"/> | <img src="https://cdn-1258574687.cos.ap-shanghai.myqcloud.com/img/%2F2025%2F07%2F17%2Fed4f20ab8e35be51f8e84c94e6e239b4-fe4947.JPG" width="300" title="支付宝支付"/> |
 
-</div>
-
-
-| 答疑方式 | 适用场景 | 响应时间 | 详细程度 | 如何提问 |
-|---------|---------|---------|---------|---------|
-| **GitHub Issues** | 部署配置问题<br/>功能异常 | 1-2天内 | 针对性强 | 📋 **提供完整信息**:<br/>• 尽量截图<br/>• 错误日志<br/>• 系统环境等等 |
-| **公众号留言** | 快速咨询<br/>使用疑问<br/>功能了解 | 几小时 | 简要指导 | 💡 **抓住问题核心**:<br/>• 一句话描述问题<br/>• 说明想要的效果 |
-
-
+| 答疑方式 | 适用场景 | 响应时间 | 详细程度 | 社区参与度 | 如何提问 |
+|:---:|:---:|:---:|:---:|:---:|:---:|
+| **GitHub Issues** | 部署配置问题<br/>功能异常 | 1-2天内 | 针对性强 | **公开讨论**<br/>其他用户可参与<br/>问题记录可搜索 | 📋 **提供完整信息**:<br/>• 尽量截图<br/>• 错误日志<br/>• 系统环境等等 |
+| **公众号交流** | 快速咨询<br/>使用疑问<br/>功能了解 | 几小时 | 简要指导 | 可以文章下留言<br/>也可以私信交流 | 💡 **抓住问题核心**:<br/>• 一句话描述问题<br/>• 说明想要的效果 |
 
 ### 项目相关
 

+ 1 - 1
version

@@ -1 +1 @@
-2.2.0
+2.3.0