imap_stream (Фаза 1): IDLE-цикл с реконнектом + SQLite mailbox.db; фикс токена TG (vesti/.env всегда); фильтр актуальности urgent (>=3 дн пропуск); HERMES_REAL_HOME в imap_client

This commit is contained in:
2026-09-15 18:34:37 +00:00
parent defcf53d75
commit 5a81c86599
3 changed files with 692 additions and 3 deletions
+39 -2
View File
@@ -74,11 +74,16 @@ if load_dotenv:
load_dotenv(BASE_DIR / ".env", override=False)
# radicale/.env — фактический источник RADICALE_PASS (проектного .env нет)
load_dotenv(BASE_DIR / "radicale" / ".env", override=False)
# Токен Telegram живёт в /opt/vesti/.env (проект-источник бота @dedinit_vesti).
# Загружаем ВСЕГДА (и при наличии python-dotenv, и без него), иначе
# VESTI_BOT_TOKEN никогда не попадает в env и все urgent-уведомления
# падают с «Нет токена Telegram».
vesti_env = Path("/opt/vesti/.env")
if vesti_env.exists():
load_dotenv(vesti_env, override=False)
else:
_load_env_file(BASE_DIR / ".env")
_load_env_file(BASE_DIR / "radicale" / ".env")
# Токен Telegram живёт в /opt/vesti/.env (проект-источник бота @dedinit_vesti);
# опционально: если файл есть, берём VESTI_BOT_TOKEN/TELEGRAM_CHAT_ID оттуда.
vesti_env = Path("/opt/vesti/.env")
if vesti_env.exists():
_load_env_file(vesti_env)
@@ -103,6 +108,10 @@ TG_API = "https://api.telegram.org"
LLM_TIMEOUT = 20
MAX_PREVIEW_CHARS = 400 # превью письма для Telegram
TG_TIMEOUT = 20
# Письмо считается актуальным для urgent-уведомления, если оно не старше
# этого срока (дни). Более старые НЕ шлём (пользователь просил не спамить
# устаревшими срочными), но помечаем handled_urgent, чтобы не перебирать.
URGENT_MAX_AGE_DAYS = 3
# ---------------------------------------------------------------------------
@@ -409,6 +418,29 @@ def format_date_for_tg(date_str):
return s
def is_urgent_recent(headers):
"""Актуально ли письмо для urgent-уведомления (не старше URGENT_MAX_AGE_DAYS).
Сравнивает дату письма (frontmatter date / internal_date) с текущей.
Если дату не удалось распарсить — считаем актуальным (бить тревогу лучше,
чем молчать). Возвращает bool.
"""
from datetime import datetime, timedelta
s = (headers.get("date") or headers.get("internal_date") or "").strip()
if not s:
return True # нет даты — не фильтруем
# форматы: '2026-09-02 10:49+03:00' | '2026-09-02T10:49:03+03:00'
m = re.match(r"^(\d{4})-(\d{2})-(\d{2})", s)
if not m:
return True
try:
d = datetime(int(m.group(1)), int(m.group(2)), int(m.group(3)))
except ValueError:
return True
limit = datetime.now() - timedelta(days=URGENT_MAX_AGE_DAYS)
return d >= limit
def handle_urgent(path, headers, body):
"""Отправить уведомление в Telegram. Возвращает (ok, detail)."""
subject = (headers.get("subject") or "(без темы)").strip()
@@ -416,6 +448,11 @@ def handle_urgent(path, headers, body):
# Дата/время получения — из frontmatter, формат ДД.ММ.ГГГГ ЧЧ:ММ
date = format_date_for_tg(headers.get("date") or headers.get("internal_date"))
date_line = f"📅 {date}\n" if date else ""
# ФИЛЬТР АКТУАЛЬНОСТИ: старые письма не шлём (см. URGENT_MAX_AGE_DAYS).
if not is_urgent_recent(headers):
return True, f"пропущено: письмо старше {URGENT_MAX_AGE_DAYS} дн (актуальность истекла)"
preview = body.strip()
if len(preview) > MAX_PREVIEW_CHARS:
preview = preview[:MAX_PREVIEW_CHARS].rstrip() + "…"