feat: 新增企微已推送新闻 link 去重缓存
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -46,7 +46,7 @@ def _cn_enabled() -> bool:
|
||||
|
||||
|
||||
def _hours_window() -> int:
|
||||
return max(1, env_int("DAILY_AI_NEWS_HOURS", 72))
|
||||
return max(1, env_int("DAILY_AI_NEWS_HOURS", 24))
|
||||
|
||||
|
||||
def _per_feed_limit() -> int:
|
||||
@@ -497,7 +497,7 @@ def _format_news_section(
|
||||
return lines
|
||||
|
||||
|
||||
def prepare_wecom_news_items(news: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
def prepare_wecom_news_items(news: dict[str, Any], *, date_str: str | None = None) -> list[dict[str, Any]]:
|
||||
if not news.get("enabled"):
|
||||
return []
|
||||
limit = _wecom_limit()
|
||||
@@ -528,10 +528,14 @@ def prepare_wecom_news_items(news: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"desc_short": _clean_text(item.get("summary", ""), 36),
|
||||
}
|
||||
)
|
||||
if date_str:
|
||||
from daily.news.pushed_links import filter_unpushed_items
|
||||
|
||||
items = filter_unpushed_items(items, date_str=date_str)
|
||||
return items
|
||||
|
||||
|
||||
def prepare_wecom_cn_news_items(news: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
def prepare_wecom_cn_news_items(news: dict[str, Any], *, date_str: str | None = None) -> list[dict[str, Any]]:
|
||||
if not news.get("enabled"):
|
||||
return []
|
||||
limit = _wecom_cn_limit()
|
||||
@@ -577,4 +581,8 @@ def prepare_wecom_cn_news_items(news: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"desc_short": _clean_text(item.get("summary", ""), 36),
|
||||
}
|
||||
)
|
||||
if date_str:
|
||||
from daily.news.pushed_links import filter_unpushed_items
|
||||
|
||||
items = filter_unpushed_items(items, date_str=date_str)
|
||||
return items
|
||||
|
||||
87
daily/news/pushed_links.py
Normal file
87
daily/news/pushed_links.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""已推送企微早报的新闻 link 去重缓存。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from daily.config import CACHE_DIR, news_dedup_days
|
||||
from daily.news.fetch import _normalize_link
|
||||
|
||||
|
||||
def _cache_path() -> Path:
|
||||
return CACHE_DIR / "pushed-news-links.json"
|
||||
|
||||
|
||||
def _load_raw() -> dict[str, Any]:
|
||||
path = _cache_path()
|
||||
if not path.exists():
|
||||
return {"dates": {}}
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, ValueError):
|
||||
return {"dates": {}}
|
||||
if not isinstance(data.get("dates"), dict):
|
||||
return {"dates": {}}
|
||||
return data
|
||||
|
||||
|
||||
def _save_raw(data: dict[str, Any]) -> None:
|
||||
path = _cache_path()
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
|
||||
def _prune(data: dict[str, Any], *, keep_days: int) -> None:
|
||||
dates: dict[str, list[str]] = data.setdefault("dates", {})
|
||||
try:
|
||||
anchor = max(datetime.strptime(d, "%Y-%m-%d") for d in dates)
|
||||
except ValueError:
|
||||
return
|
||||
cutoff = anchor - timedelta(days=keep_days)
|
||||
for key in list(dates.keys()):
|
||||
try:
|
||||
if datetime.strptime(key, "%Y-%m-%d") < cutoff:
|
||||
dates.pop(key, None)
|
||||
except ValueError:
|
||||
dates.pop(key, None)
|
||||
|
||||
|
||||
def load_pushed_link_set() -> set[str]:
|
||||
data = _load_raw()
|
||||
out: set[str] = set()
|
||||
for links in (data.get("dates") or {}).values():
|
||||
if isinstance(links, list):
|
||||
out.update(str(x) for x in links if x)
|
||||
return out
|
||||
|
||||
|
||||
def filter_unpushed_items(
|
||||
items: list[dict[str, Any]],
|
||||
*,
|
||||
date_str: str,
|
||||
) -> list[dict[str, Any]]:
|
||||
del date_str # reserved for per-day scoping if needed later
|
||||
seen = load_pushed_link_set()
|
||||
out: list[dict[str, Any]] = []
|
||||
for item in items:
|
||||
link = _normalize_link(str(item.get("link") or ""))
|
||||
if not link or link in seen:
|
||||
continue
|
||||
out.append(item)
|
||||
return out
|
||||
|
||||
|
||||
def record_pushed_links(date_str: str, links: list[str]) -> None:
|
||||
data = _load_raw()
|
||||
dates: dict[str, list[str]] = data.setdefault("dates", {})
|
||||
normalized: list[str] = []
|
||||
for link in links:
|
||||
clean = _normalize_link(link)
|
||||
if clean:
|
||||
normalized.append(clean)
|
||||
dates[date_str] = sorted(set(normalized))
|
||||
_prune(data, keep_days=news_dedup_days())
|
||||
_save_raw(data)
|
||||
Reference in New Issue
Block a user