fix: deduplicate SunnyPT site messages

This commit is contained in:
jxxghp
2026-07-23 14:12:41 +08:00
parent 3dde94be0f
commit 297cd04fbc
5 changed files with 98 additions and 20 deletions

View File

@@ -78,23 +78,7 @@ class SiteChain(ChainBase):
eventmanager.send_event(EventType.SiteRefreshed, {
"site_id": site.get("id")
})
# 发送站点消息
if userdata.message_unread:
if userdata.message_unread_contents and len(userdata.message_unread_contents) > 0:
for head, date, content in userdata.message_unread_contents:
msg_title = f"【站点 {site.get('name')} 消息】"
msg_text = f"时间:{date}\n标题:{head}\n内容:\n{content}"
self.post_message(Notification(
mtype=NotificationType.SiteMessage,
title=msg_title, text=msg_text, link=site.get("url")
))
else:
self.post_message(Notification(
mtype=NotificationType.SiteMessage,
title=f"站点 {site.get('name')} 收到 "
f"{userdata.message_unread} 条新消息,请登陆查看",
link=site.get("url")
))
self._post_site_messages(site=site, userdata=userdata)
# 低分享率警告
if userdata.ratio and float(userdata.ratio) < 1 and not bool(
re.search(r"(贵宾|VIP?)", userdata.user_level or "", re.IGNORECASE)):
@@ -105,6 +89,38 @@ class SiteChain(ChainBase):
))
return userdata
def _post_site_messages(self, site: dict, userdata: SiteUserData) -> None:
"""
发送站点未读消息,并按解析器提供的来源标识做持久化去重。
:param site: 站点索引配置
:param userdata: 本次刷新的站点用户数据
"""
if not userdata.message_unread:
return
if not userdata.message_unread_contents:
self.post_message(Notification(
mtype=NotificationType.SiteMessage,
title=f"站点 {site.get('name')} 收到 "
f"{userdata.message_unread} 条新消息,请登陆查看",
link=site.get("url")
))
return
for message in userdata.message_unread_contents:
head, date, content, *metadata = message
message_source = metadata[0] if metadata else None
if message_source and self.messageoper.exists_by_source(message_source):
continue
msg_title = f"【站点 {site.get('name')} 消息】"
msg_text = f"时间:{date}\n标题:{head}\n内容:\n{content}"
self.post_message(Notification(
source=message_source,
mtype=NotificationType.SiteMessage,
title=msg_title,
text=msg_text,
link=site.get("url")
))
def refresh_userdatas(
self,
progress_callback: Optional[Callable[..., None]] = None,

View File

@@ -105,6 +105,15 @@ class MessageOper(DbOper):
"""
return Message.list_by_page(self._db, page, count)
def exists_by_source(self, source: str) -> bool:
"""
判断指定来源标识的消息记录是否存在。
:param source: 消息来源唯一标识
:return: 是否存在匹配记录
"""
return Message.exists_by_source(self._db, source)
async def async_list_by_page(
self, page: Optional[int] = 1, count: Optional[int] = 30
) -> list[Message]:

View File

@@ -62,6 +62,18 @@ class Message(Base):
.all()
)
@classmethod
@db_query
def exists_by_source(cls, db: Session, source: str) -> bool:
"""
判断指定来源标识的消息记录是否存在。
:param db: 数据库会话
:param source: 消息来源唯一标识
:return: 是否存在匹配记录
"""
return db.query(cls.id).filter(cls.source == source).first() is not None
@classmethod
@async_db_query
async def async_list_by_page(

View File

@@ -123,8 +123,12 @@ class SunnyPTSiteUserInfo(SiteParserBase):
title = message.get("title")
content = message.get("content")
created_at = StringUtils.unify_datetime_str(message.get("created_at"))
message_id = message.get("id")
if title and content and created_at:
self.message_unread_contents.append((title, created_at, content))
message_source = f"sunnypt-message:{message_id}" if message_id is not None else None
self.message_unread_contents.append(
(title, created_at, content, message_source)
)
return "next" if messages_data.get("has_more") else None
def _parse_user_traffic_info(self, html_text: str) -> None:

View File

@@ -9,10 +9,11 @@ from app.chain.download import DownloadChain
from app.chain.site import SiteChain
from app.core.config import settings
from app.core.context import TorrentInfo
from app.db.message_oper import MessageOper
from app.modules.indexer import IndexerModule
from app.modules.indexer.parser.sunnypt import SunnyPTSiteUserInfo
from app.modules.indexer.spider.sunnypt import SunnyPTSpider
from app.schemas import MediaType
from app.schemas import MediaType, NotificationType
class _FakeResponse:
@@ -286,11 +287,47 @@ def test_sunnypt_user_parser_reads_profile_and_messages_without_marking_read(mon
assert parser.leeching_size == 10737418240
assert parser.message_unread == 1
assert parser.message_unread_contents == [
("种子审核通过", "2026-07-21 16:30:00", "你发布的种子已审核通过。")
(
"种子审核通过",
"2026-07-21 16:30:00",
"你发布的种子已审核通过。",
"sunnypt-message:9001",
)
]
assert not any(url.endswith("/read") or url.endswith("/read-all") for url in requested_urls)
def test_site_messages_are_deduplicated_by_persisted_source(monkeypatch):
"""站点消息应使用解析器保留的消息 ID 来源标识做持久化去重。"""
duplicate_source = "sunnypt-message:9001-dedup-test"
MessageOper().add(
source=duplicate_source,
mtype=NotificationType.SiteMessage,
title="existing",
text="existing",
)
sent_messages = []
chain = object.__new__(SiteChain)
chain.messageoper = MessageOper()
monkeypatch.setattr(chain, "post_message", sent_messages.append)
userdata = SimpleNamespace(
message_unread=2,
message_unread_contents=[
("重复消息", "2026-07-21 16:30:00", "旧内容", duplicate_source),
("新消息", "2026-07-22 16:30:00", "新内容", "sunnypt-message:9002-dedup-test"),
],
)
chain._post_site_messages(
site={"name": "Sunny", "url": "https://sunnypt.top/"},
userdata=userdata,
)
assert len(sent_messages) == 1
assert sent_messages[0].source == "sunnypt-message:9002-dedup-test"
assert sent_messages[0].title == "【站点 Sunny 消息】"
def test_indexer_module_dispatches_sunnypt_search(monkeypatch):
"""IndexerModule 应把 SunnyPT 同步搜索参数交给专用 API Spider。"""
captured = {}