mirror of
https://github.com/Nezumi-2711/astrbot_plugin_qq_group_daily_analysis.git
synced 2026-09-22 13:38:43 +00:00
style(pre-commit)
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -91,4 +91,3 @@ def _register_adapters():
|
||||
|
||||
|
||||
_register_adapters()
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user