Per docs/plugin-audit-report.md (plugins normalized to Trigger(handlers) → Service(services) → Model(repository/models) + utils): - Split monolithic __init__.py into handlers/services/utils across dailywife, deer_pipe, dice, galgame_card, helldivers_tools, huoziyinshua, learning_chat, makeaquote, mc_server_status, ncm_saying, picfinder_take, picstatus, random_jm_code, regif, steam_info, video_analysis, group_tools - Move static assets under res/: deer_pipe font/img, makeaquote font, helldivers img/templates, huoziyinshua HuoZiYinShua - Add config.py + register_config_items to ncm_saying, random_jm_code, group_tools; learning_chat unified config bridge - Remove deprecated: voice_trans plugin, bf_bot/test.py, dead code in dailywife/deer_pipe, empty dirs, debug scripts under helldivers temp - Disable brash_general_supercredits_tools (stub comment only) - bot.py: optional stdout/stderr redirect to log file for Web log viewer, force ANSI colorize on non-TTY sinks - Move runtime data (jm_code.json) out of plugin dir into hexi/data - Docs: plugin-audit-report.md; README reflects removed plugins Co-Authored-By: Claude <noreply@anthropic.com>
78 lines
2.5 KiB
Python
78 lines
2.5 KiB
Python
"""群聊人设卡 —— 采集编排(Service 层)。
|
|
|
|
把 __init__.py 里的采样/复读/刷屏检测与落库编排抽到此处,使入口更薄。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import time
|
|
from collections import deque
|
|
|
|
from nonebot import get_driver
|
|
|
|
from .. import processor, repository
|
|
|
|
# 采样/复读检测的内存状态(不进库)
|
|
_recent_texts: dict[int, deque[str]] = {} # group_id -> 最近消息文本
|
|
_last_store: dict[tuple[int, int], float] = {} # (group_id, user_id) -> 上次落库时间戳
|
|
|
|
|
|
def _is_repeat(group_id: int, content: str) -> bool:
|
|
"""复读检测:内容在最近 N 条里出现过则丢弃"""
|
|
queue = _recent_texts.setdefault(group_id, deque(maxlen=processor.REPEAT_WINDOW))
|
|
if content in queue:
|
|
return True
|
|
queue.append(content)
|
|
return False
|
|
|
|
|
|
def _throttled(group_id: int, user_id: int) -> bool:
|
|
"""刷屏采样:5 秒内同一个人只落库一次"""
|
|
key = (group_id, user_id)
|
|
now = time.time()
|
|
if now - _last_store.get(key, 0) < processor.THROTTLE_SECONDS:
|
|
return True
|
|
_last_store[key] = now
|
|
return False
|
|
|
|
|
|
async def collect(event):
|
|
"""鉴权 + 治理 + 采样 + 落库。"""
|
|
if not await repository.is_group_enabled(event.group_id):
|
|
return
|
|
if not await repository.is_joined(event.user_id, event.group_id):
|
|
return
|
|
if event.user_id == event.self_id:
|
|
return
|
|
|
|
command_starts = get_driver().config.command_start
|
|
if not processor.has_recordable_content(event):
|
|
return
|
|
text = processor.filter_content(
|
|
processor.extract_text(event), command_starts=command_starts
|
|
)
|
|
image_hashes = processor.extract_image_hashes(event)
|
|
content = processor.build_content(
|
|
text or "", len(image_hashes), processor.count_faces(event)
|
|
)
|
|
if content is None:
|
|
return
|
|
context = " ".join(_recent_texts.get(event.group_id, ()))
|
|
content = processor.desensitize(content, context)
|
|
if _is_repeat(event.group_id, content):
|
|
return
|
|
if _throttled(event.group_id, event.user_id):
|
|
return
|
|
|
|
await repository.add_chat_log(
|
|
user_id=event.user_id,
|
|
group_id=event.group_id,
|
|
content=content,
|
|
nickname=event.sender.card or event.sender.nickname or "",
|
|
target_user_id=processor.extract_target_user_id(event),
|
|
reply_to_content=processor.extract_reply_to_content(event),
|
|
image_count=len(image_hashes),
|
|
image_hashes=json.dumps(image_hashes, ensure_ascii=False) if image_hashes else None,
|
|
)
|