style(pre-commit)

This commit is contained in:
SXP-Simon
2026-02-11 13:33:43 +08:00
parent ec3f6b0b45
commit 8401283314
10 changed files with 80 additions and 62 deletions
+2 -2
View File
@@ -18,17 +18,17 @@ from .src.application.services.analysis_application_service import (
AnalysisApplicationService,
)
from .src.domain.services.analysis_domain_service import AnalysisDomainService
from .src.domain.services.incremental_merge_service import IncrementalMergeService
from .src.domain.services.statistics_service import StatisticsService
from .src.infrastructure.analysis.llm_analyzer import LLMAnalyzer
from .src.infrastructure.config.config_manager import ConfigManager
from .src.infrastructure.persistence.history_manager import HistoryManager
from .src.infrastructure.persistence.incremental_store import IncrementalStore
from .src.infrastructure.platform.bot_manager import BotManager
from .src.infrastructure.reporting.generators import ReportGenerator
from .src.infrastructure.scheduler.auto_scheduler import AutoScheduler
from .src.infrastructure.scheduler.retry import RetryManager
from .src.utils.pdf_utils import PDFInstaller
from .src.infrastructure.persistence.incremental_store import IncrementalStore
from .src.domain.services.incremental_merge_service import IncrementalMergeService
class QQGroupDailyAnalysis(Star):
+30 -21
View File
@@ -63,11 +63,13 @@ class IncrementalBatch:
golden_quotes: list[dict] = field(default_factory=list)
# Token 消耗
token_usage: dict = field(default_factory=lambda: {
"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0,
})
token_usage: dict = field(
default_factory=lambda: {
"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0,
}
)
# 增量追踪
last_message_timestamp: int = 0
@@ -107,11 +109,14 @@ class IncrementalBatch:
emoji_stats=data.get("emoji_stats", {}),
topics=data.get("topics", []),
golden_quotes=data.get("golden_quotes", []),
token_usage=data.get("token_usage", {
"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0,
}),
token_usage=data.get(
"token_usage",
{
"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0,
},
),
last_message_timestamp=data.get("last_message_timestamp", 0),
participant_ids=data.get("participant_ids", []),
)
@@ -179,11 +184,13 @@ class IncrementalState:
total_message_count: int = 0
total_character_count: int = 0
total_analysis_count: int = 0
total_token_usage: dict = field(default_factory=lambda: {
"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0,
})
total_token_usage: dict = field(
default_factory=lambda: {
"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0,
}
)
# 增量跟踪
last_analyzed_message_timestamp: int = 0
@@ -237,12 +244,14 @@ class IncrementalState:
"""
users = []
for user_id, data in self.user_activities.items():
users.append({
"user_id": user_id,
"name": data.get("name", user_id),
"message_count": data.get("message_count", 0),
"char_count": data.get("char_count", 0),
})
users.append(
{
"user_id": user_id,
"name": data.get("name", user_id),
"message_count": data.get("message_count", 0),
"char_count": data.get("char_count", 0),
}
)
users.sort(key=lambda x: x["message_count"], reverse=True)
return users[:top_n]
+3 -1
View File
@@ -13,7 +13,9 @@ class SummaryTopic:
topic: str
contributors: list[str]
detail: str
contributor_ids: list[str] = field(default_factory=list) # 贡献者ID列表 (用于显示头像)
contributor_ids: list[str] = field(
default_factory=list
) # 贡献者ID列表 (用于显示头像)
@dataclass
@@ -353,12 +353,14 @@ class TopicAnalyzer(BaseAnalyzer):
# 后处理:contributors 此时包含的是 ID,需要映射回昵称
for topic in topics:
raw_ids = topic.contributors # LLM 返回的是 ID 列表
# 填充 contributor_ids
# 过滤掉非数字的脏数据 (LLM 偶尔会发疯)
valid_ids = [str(uid).strip() for uid in raw_ids if str(uid).strip().isdigit()]
valid_ids = [
str(uid).strip() for uid in raw_ids if str(uid).strip().isdigit()
]
topic.contributor_ids = valid_ids
# 映射回昵称用于显示
resolved_names = []
for uid in valid_ids:
@@ -370,9 +372,9 @@ class TopicAnalyzer(BaseAnalyzer):
if uid in bot_ids:
name = "Bot"
else:
name = uid # Fallback to ID
name = uid # Fallback to ID
resolved_names.append(name)
topic.contributors = resolved_names
return topics, usage
@@ -13,7 +13,6 @@ KV 键设计:
值: int (epoch timestamp)
"""
import time
from typing import Any
from ...domain.entities.incremental_state import IncrementalBatch
@@ -131,10 +130,12 @@ class IncrementalStore:
# 2. 更新索引
index = await self._get_index(group_id)
index.append({
"batch_id": batch.batch_id,
"timestamp": batch.timestamp,
})
index.append(
{
"batch_id": batch.batch_id,
"timestamp": batch.timestamp,
}
)
await self._save_index(group_id, index)
logger.debug(
@@ -173,7 +174,8 @@ class IncrementalStore:
# 筛选在窗口范围内的批次
matching_entries = [
entry for entry in index
entry
for entry in index
if window_start <= entry.get("timestamp", 0) <= window_end
]
@@ -247,22 +249,16 @@ class IncrementalStore:
key = self._last_ts_key(group_id)
try:
await self.plugin.put_kv_data(key, timestamp)
logger.debug(
f"更新最后分析时间戳: 群 {group_id}, ts={timestamp}"
)
logger.debug(f"更新最后分析时间戳: 群 {group_id}, ts={timestamp}")
except Exception as e:
logger.error(
f"更新最后分析时间戳失败 (Key: {key}): {e}", exc_info=True
)
logger.error(f"更新最后分析时间戳失败 (Key: {key}): {e}", exc_info=True)
raise
# ================================================================
# 过期批次清理
# ================================================================
async def cleanup_old_batches(
self, group_id: str, before_timestamp: float
) -> int:
async def cleanup_old_batches(self, group_id: str, before_timestamp: float) -> int:
"""
清理指定群中早于给定时间戳的所有批次。
@@ -337,9 +333,7 @@ class IncrementalStore:
index = await self._get_index(group_id)
return len(index)
async def get_all_batch_summaries(
self, group_id: str
) -> list[dict]:
async def get_all_batch_summaries(self, group_id: str) -> list[dict]:
"""
获取指定群所有批次的摘要信息(不加载完整数据)。
@@ -355,4 +349,3 @@ class IncrementalStore:
# 按时间戳升序排列
index.sort(key=lambda x: x.get("timestamp", 0))
return index
@@ -399,8 +399,9 @@ class DiscordAdapter(PlatformAdapter):
file_to_send = None
if image_path.startswith("base64://"):
# Base64 图片:解码 -> 内存 Object -> Discord
from io import BytesIO
import base64 # Fix: Ensure base64 is imported
from io import BytesIO
try:
base64_data = image_path.split("base64://")[1]
image_bytes = base64.b64decode(base64_data)
@@ -52,7 +52,7 @@ class TelegramAdapter(PlatformAdapter):
def __init__(self, bot_instance: Any, config: dict | None = None):
super().__init__(bot_instance, config)
self._cached_client: ExtBot | None = None
self._context: "Context | None" = None
self._context: Context | None = None
# 机器人自身 ID(用于消息过滤)
self.bot_user_id = str(config.get("bot_user_id", "")) if config else ""
@@ -610,15 +610,15 @@ class TelegramAdapter(PlatformAdapter):
# 格式: https://api.telegram.org/file/bot<token>/<file_path>
# python-telegram-bot 的 File.file_path 属性通常只返回路径部分
# 需要手动拼接或使用 instance.file.file_path (取决于版本)
file_path = file.file_path
if file_path.startswith("http"):
return file_path
# 尝试构建完整 URL
if hasattr(client, "token"):
return f"https://api.telegram.org/file/bot{client.token}/{file_path}"
# 如果无法获取 token,返回 None
return None
return None
@@ -655,10 +655,10 @@ class TelegramAdapter(PlatformAdapter):
file_path = file.file_path
if file_path.startswith("http"):
return file_path
if hasattr(client, "token"):
return f"https://api.telegram.org/file/bot{client.token}/{file_path}"
return None
return None
except Exception as e:
-1
View File
@@ -91,4 +91,3 @@ def _register_adapters():
_register_adapters()
+4 -1
View File
@@ -79,7 +79,10 @@ class ReportDispatcher:
return None
image_url, html_content = await self.report_generator.generate_image_report(
analysis_result, group_id, self._html_render_func, avatar_getter=avatar_getter
analysis_result,
group_id,
self._html_render_func,
avatar_getter=avatar_getter,
)
except Exception as e:
logger.error(f"[{trace_id}] Failed to generate image report: {e}")
+14 -5
View File
@@ -179,17 +179,26 @@ class RetryManager:
# 本地文件路径
try:
import os
if os.path.exists(image_data):
with open(image_data, "rb") as f:
image_data = f.read()
# 校验文件头 (防御性编程,避免发送错误文本)
if not image_data.startswith(b"\xff\xd8") and not image_data.startswith(b"\x89PNG"):
if len(image_data) < 1024 and (b"Error" in image_data or b"Exception" in image_data):
logger.error(f"[RetryManager] 渲染器生成了错误文件而非图片: {image_data.decode('utf-8', errors='ignore')}")
if not image_data.startswith(
b"\xff\xd8"
) and not image_data.startswith(b"\x89PNG"):
if len(image_data) < 1024 and (
b"Error" in image_data or b"Exception" in image_data
):
logger.error(
f"[RetryManager] 渲染器生成了错误文件而非图片: {image_data.decode('utf-8', errors='ignore')}"
)
return False
else:
logger.error(f"[RetryManager] 渲染器返回的路径不存在: {image_data}")
logger.error(
f"[RetryManager] 渲染器返回的路径不存在: {image_data}"
)
image_data = None
except Exception as e:
logger.error(f"[RetryManager] 读取本地图片失败: {e}")