feat: add PUSH_RECENCY_HOURS (12h) and TELEGRAM_PUSH_DELAY_SEC (1.5s)
- Skip emails older than 12 hours to prevent flooding on re-enable - Add 1.5s delay between Telegram sends to avoid rate limiting - Update tests T-19/T-20/T-22 to use recent timestamps and mock delay - Add 2 new recency filter tests (old email skipped, recent passes) - 221 tests passing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
1 parent
c0381f6bf5
commit
308494dcc9
2 files changed
+92
-12
No files matched your search
@@ -21,6 +21,8 @@ MAX_TELEGRAM_LENGTH = 4096
|
||||
MAX_PREVIEW_LENGTH = 200
|
||||
MAX_EMAILS_PER_FETCH = 50
|
||||
MAX_SENT_PER_JOB = 20
|
||||
PUSH_RECENCY_HOURS = 12 # 超过此小时数的邮件不推送(防止首次启用时轰炸)
|
||||
TELEGRAM_PUSH_DELAY_SEC = 1.5 # 连续发送 Telegram 消息的间隔(防限流)
|
||||
|
||||
|
||||
def _escape_html(text: str) -> str:
|
||||
@@ -355,6 +357,9 @@ def run_telegram_push_job(app) -> None:
|
||||
return
|
||||
|
||||
job_start_time = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")
|
||||
# 计算 recency 截止时间(超过此时间的邮件不推送)
|
||||
from datetime import timedelta
|
||||
recency_cutoff = (datetime.now(timezone.utc) - timedelta(hours=PUSH_RECENCY_HOURS)).strftime("%Y-%m-%dT%H:%M:%S")
|
||||
sent_count = 0
|
||||
|
||||
# 并行获取所有账号邮件
|
||||
@@ -382,9 +387,15 @@ def run_telegram_push_job(app) -> None:
|
||||
for em in sorted(emails, key=lambda e: e.get("received_at", "")):
|
||||
if sent_count >= MAX_SENT_PER_JOB:
|
||||
break
|
||||
# 跳过超过 PUSH_RECENCY_HOURS 的旧邮件
|
||||
if em.get("received_at", "") < recency_cutoff:
|
||||
continue
|
||||
msg = _build_telegram_message(account["email"], em)
|
||||
if _send_telegram_message(bot_token, chat_id, msg):
|
||||
sent_count += 1
|
||||
# 消息间延迟,防止 Telegram API 限流
|
||||
if TELEGRAM_PUSH_DELAY_SEC > 0:
|
||||
time.sleep(TELEGRAM_PUSH_DELAY_SEC)
|
||||
|
||||
update_telegram_cursor(account["id"], job_start_time)
|
||||
|
||||
|
||||
+81
-12
@@ -17,6 +17,7 @@
|
||||
|
||||
import json
|
||||
import unittest
|
||||
from datetime import datetime, timezone
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from tests._import_app import clear_login_attempts, import_web_app_module
|
||||
@@ -444,12 +445,16 @@ class TestRunTelegramPushJob(unittest.TestCase):
|
||||
)
|
||||
self._set_settings()
|
||||
|
||||
# 使用近期时间(PUSH_RECENCY_HOURS 内)
|
||||
from datetime import timedelta
|
||||
recent = (datetime.now(timezone.utc) - timedelta(minutes=5)).strftime("%Y-%m-%dT%H:%M:%S")
|
||||
emails = [
|
||||
self._make_email("2026-03-04T10:00:00"),
|
||||
self._make_email("2026-03-04T11:00:00"),
|
||||
self._make_email(recent),
|
||||
self._make_email(recent),
|
||||
]
|
||||
with patch("outlook_web.services.telegram_push._fetch_new_emails_imap", return_value=emails), \
|
||||
patch("outlook_web.services.telegram_push._send_telegram_message", return_value=True) as mock_send:
|
||||
patch("outlook_web.services.telegram_push._send_telegram_message", return_value=True) as mock_send, \
|
||||
patch("outlook_web.services.telegram_push.TELEGRAM_PUSH_DELAY_SEC", 0):
|
||||
|
||||
self._run_job()
|
||||
|
||||
@@ -473,8 +478,10 @@ class TestRunTelegramPushJob(unittest.TestCase):
|
||||
_insert_test_account(db, "bb@tgjob.com", enabled=1, last_checked="2026-01-01T00:00:00")
|
||||
self._set_settings()
|
||||
|
||||
emails_a = [self._make_email(f"2026-03-04T10:{i:02d}:00") for i in range(12)]
|
||||
emails_b = [self._make_email(f"2026-03-04T11:{i:02d}:00") for i in range(15)]
|
||||
from datetime import timedelta
|
||||
recent = (datetime.now(timezone.utc) - timedelta(minutes=5)).strftime("%Y-%m-%dT%H:%M:%S")
|
||||
emails_a = [self._make_email(recent) for _ in range(12)]
|
||||
emails_b = [self._make_email(recent) for _ in range(15)]
|
||||
|
||||
call_count = 0
|
||||
|
||||
@@ -490,7 +497,8 @@ class TestRunTelegramPushJob(unittest.TestCase):
|
||||
return True
|
||||
|
||||
with patch("outlook_web.services.telegram_push._fetch_new_emails_imap", side_effect=fake_fetch), \
|
||||
patch("outlook_web.services.telegram_push._send_telegram_message", side_effect=fake_send):
|
||||
patch("outlook_web.services.telegram_push._send_telegram_message", side_effect=fake_send), \
|
||||
patch("outlook_web.services.telegram_push.TELEGRAM_PUSH_DELAY_SEC", 0):
|
||||
self._run_job()
|
||||
|
||||
self.assertEqual(call_count, 20, f"Expected 20, got {call_count}")
|
||||
@@ -534,9 +542,12 @@ class TestRunTelegramPushJob(unittest.TestCase):
|
||||
)
|
||||
self._set_settings()
|
||||
|
||||
emails = [self._make_email("2026-03-04T10:00:00"), self._make_email("2026-03-04T11:00:00")]
|
||||
from datetime import timedelta
|
||||
recent = (datetime.now(timezone.utc) - timedelta(minutes=5)).strftime("%Y-%m-%dT%H:%M:%S")
|
||||
emails = [self._make_email(recent), self._make_email(recent)]
|
||||
with patch("outlook_web.services.telegram_push._fetch_new_emails_imap", return_value=emails), \
|
||||
patch("outlook_web.services.telegram_push._send_telegram_message", return_value=False):
|
||||
patch("outlook_web.services.telegram_push._send_telegram_message", return_value=False), \
|
||||
patch("outlook_web.services.telegram_push.TELEGRAM_PUSH_DELAY_SEC", 0):
|
||||
try:
|
||||
self._run_job()
|
||||
except Exception as e:
|
||||
@@ -984,7 +995,9 @@ class TestParallelJobBehavior(unittest.TestCase):
|
||||
last_checked="2026-03-01T00:00:00")
|
||||
self._set_settings()
|
||||
|
||||
emails = [self._make_email("2026-03-04T10:00:00")]
|
||||
from datetime import timedelta
|
||||
recent = (datetime.now(timezone.utc) - timedelta(minutes=5)).strftime("%Y-%m-%dT%H:%M:%S")
|
||||
emails = [self._make_email(recent)]
|
||||
|
||||
send_calls = []
|
||||
def fake_send(token, chat, msg):
|
||||
@@ -992,7 +1005,8 @@ class TestParallelJobBehavior(unittest.TestCase):
|
||||
return False # 发送失败
|
||||
|
||||
with patch("outlook_web.services.telegram_push._fetch_new_emails_imap", return_value=emails), \
|
||||
patch("outlook_web.services.telegram_push._send_telegram_message", side_effect=fake_send):
|
||||
patch("outlook_web.services.telegram_push._send_telegram_message", side_effect=fake_send), \
|
||||
patch("outlook_web.services.telegram_push.TELEGRAM_PUSH_DELAY_SEC", 0):
|
||||
self._run_job()
|
||||
|
||||
self.assertEqual(len(send_calls), 1, "应尝试发送 1 次")
|
||||
@@ -1008,7 +1022,9 @@ class TestParallelJobBehavior(unittest.TestCase):
|
||||
last_checked="2026-03-01T00:00:00")
|
||||
self._set_settings()
|
||||
|
||||
emails = [self._make_email("2026-03-04T10:00:00")]
|
||||
from datetime import timedelta
|
||||
recent = (datetime.now(timezone.utc) - timedelta(minutes=5)).strftime("%Y-%m-%dT%H:%M:%S")
|
||||
emails = [self._make_email(recent)]
|
||||
|
||||
def fake_fetch(account, since):
|
||||
if account["email"] == "ok@parallel.com":
|
||||
@@ -1016,7 +1032,8 @@ class TestParallelJobBehavior(unittest.TestCase):
|
||||
raise ConnectionError("IMAP timeout")
|
||||
|
||||
with patch("outlook_web.services.telegram_push._fetch_new_emails_imap", side_effect=fake_fetch), \
|
||||
patch("outlook_web.services.telegram_push._send_telegram_message", return_value=True):
|
||||
patch("outlook_web.services.telegram_push._send_telegram_message", return_value=True), \
|
||||
patch("outlook_web.services.telegram_push.TELEGRAM_PUSH_DELAY_SEC", 0):
|
||||
self._run_job()
|
||||
|
||||
ok_cursor = db.execute(
|
||||
@@ -1064,6 +1081,58 @@ class TestParallelJobBehavior(unittest.TestCase):
|
||||
interval = _get_telegram_interval(self.app)
|
||||
self.assertEqual(interval, 60)
|
||||
|
||||
def test_recency_filter_skips_old_emails(self):
|
||||
"""PUSH_RECENCY_HOURS: 超过 12 小时的邮件应被跳过"""
|
||||
with self.app.app_context():
|
||||
from outlook_web.db import get_db
|
||||
db = get_db()
|
||||
_insert_test_account(db, "old@parallel.com", enabled=1,
|
||||
last_checked="2026-01-01T00:00:00")
|
||||
self._set_settings()
|
||||
|
||||
# 创建一封很旧的邮件(比当前时间早 24 小时以上)
|
||||
from datetime import timedelta
|
||||
old_time = (datetime.now(timezone.utc) - timedelta(hours=24)).strftime("%Y-%m-%dT%H:%M:%S")
|
||||
old_emails = [self._make_email(old_time)]
|
||||
|
||||
send_calls = []
|
||||
def fake_send(token, chat, msg):
|
||||
send_calls.append(msg)
|
||||
return True
|
||||
|
||||
with patch("outlook_web.services.telegram_push._fetch_new_emails_imap", return_value=old_emails), \
|
||||
patch("outlook_web.services.telegram_push._send_telegram_message", side_effect=fake_send), \
|
||||
patch("outlook_web.services.telegram_push.TELEGRAM_PUSH_DELAY_SEC", 0):
|
||||
self._run_job()
|
||||
|
||||
self.assertEqual(len(send_calls), 0, "超过 12 小时的邮件不应推送")
|
||||
|
||||
def test_recent_email_passes_recency_filter(self):
|
||||
"""PUSH_RECENCY_HOURS: 12 小时内的邮件应正常推送"""
|
||||
with self.app.app_context():
|
||||
from outlook_web.db import get_db
|
||||
db = get_db()
|
||||
_insert_test_account(db, "new@parallel.com", enabled=1,
|
||||
last_checked="2026-01-01T00:00:00")
|
||||
self._set_settings()
|
||||
|
||||
# 创建一封 1 小时前的邮件
|
||||
from datetime import timedelta
|
||||
recent_time = (datetime.now(timezone.utc) - timedelta(hours=1)).strftime("%Y-%m-%dT%H:%M:%S")
|
||||
recent_emails = [self._make_email(recent_time)]
|
||||
|
||||
send_calls = []
|
||||
def fake_send(token, chat, msg):
|
||||
send_calls.append(msg)
|
||||
return True
|
||||
|
||||
with patch("outlook_web.services.telegram_push._fetch_new_emails_imap", return_value=recent_emails), \
|
||||
patch("outlook_web.services.telegram_push._send_telegram_message", side_effect=fake_send), \
|
||||
patch("outlook_web.services.telegram_push.TELEGRAM_PUSH_DELAY_SEC", 0):
|
||||
self._run_job()
|
||||
|
||||
self.assertEqual(len(send_calls), 1, "1 小时内的邮件应正常推送")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user