Files
HeXi/hexi/plugins/nonebot_plugin_group_daily_analysis/config.py
T
sansenhoshiandClaude Code 51b08ccb68 feat: 群分析本地消息库 / 处理中占位图 / Web 日志窗口重构
- 群分析: 新增 history_store 只读本地消息源(读 learning_chat 落库,含 uninfo
  昵称补齐/去重/截断判定),适配器优先读本地库、失败回退 OneBot 分页;分页
  锚点字段回退链修复 NapCat 传 message_seq 翻页断裂;新增「本地记录」开关
- core/message_utils: 新增 common_proc_reply 占位图通用回复(引用消息 + 处理中
  动图,支持后台任务显式指定 target),群分析/战况/倒放改用
- web_hub + web: 日志页改固定窗口滚动 + 翻页锚定 + 自动换行,SSE 日志轮转发
  reset 帧,入口 HTML no-cache,行数统计增量缓存,CPU 改非阻塞采样,登录信息缓存
- 插件内 CLAUDE.md / DESIGN.md 不入库(.gitignore),galgame_card 两份文档取消
  跟踪(文件保留在磁盘)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-14 18:59:01 +08:00

145 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""NoneBot 版插件配置加载。
配置持久化在数据目录 config.json,键值结构与原 AstrBot 配置分组一致,
这样可以直接喂给 core 的 ConfigManager(它本质只读嵌套 dict)。
"""
from __future__ import annotations
import json
import os
from pathlib import Path
from .core.shared.constants import PLUGIN_NAME
from .core.utils.paths import get_data_dir
class ConfigDict(dict):
"""兼容原 ConfigManager 的 dict;save_config 会把改动持久化到 config.json。"""
def save_config(self) -> None:
try:
config_file().write_text(
json.dumps(dict(self), ensure_ascii=False, indent=2, default=str),
encoding="utf-8",
)
except OSError:
pass
def _build_from_items(items: dict) -> dict:
"""从 schema 的 items 递归生成嵌套默认配置。"""
out: dict = {}
for key, item in items.items():
if not isinstance(item, dict):
continue
if item.get("type") == "object" and isinstance(item.get("items"), dict):
out[key] = _build_from_items(item["items"])
elif "default" in item:
out[key] = item["default"]
return out
def _schema_defaults() -> dict:
"""从插件的 _conf_schema.json 读取完整默认配置。"""
schema_path = Path(__file__).resolve().parents[0] / "_conf_schema.json"
try:
schema = json.loads(schema_path.read_text(encoding="utf-8-sig"))
except (OSError, json.JSONDecodeError):
return {}
defaults: dict = {}
for group, spec in schema.items():
if isinstance(spec, dict) and isinstance(spec.get("items"), dict):
defaults[group] = _build_from_items(spec["items"])
return defaults
def _read_config_value(*names: str, default: str = "") -> str:
"""依次从 shell 环境变量与 NoneBot driver.config(.env) 读取值。
NoneBot 的 .env 是通过 pydantic 加载的,不会写入 os.environ,
因此必须能从 get_driver().config 的小写属性读取。优先环境变量。
"""
for n in names:
v = os.environ.get(n)
if v:
return str(v)
try:
from nonebot import get_driver
drv_cfg = get_driver().config
except Exception:
return default
for n in names:
attr = n.lower()
try:
v = getattr(drv_cfg, attr, None)
except Exception:
v = None
if v:
return str(v)
return default
def _llm_env_values() -> dict:
"""用户必填的 LLM 端点/密钥/模型;AstrBot 版是 Provider 体系,插件本身不存这些。"""
return {
"llm_api_base": _read_config_value("HEXI_LLM_API_BASE"),
"llm_api_key": _read_config_value("HEXI_LLM_API_KEY"),
"llm_model": _read_config_value("HEXI_LLM_MODEL"),
}
def _default_config() -> dict:
cfg = _schema_defaults()
cfg.setdefault("llm", {}).update(_llm_env_values())
cfg.setdefault("basic", {}).setdefault("output_format", ["image"])
cfg.setdefault("basic", {}).setdefault("enable_base64_image", True)
cfg.setdefault("basic", {}).setdefault("analysis_days", 1)
cfg.setdefault("basic", {}).setdefault("max_messages", 1000)
cfg.setdefault("basic", {}).setdefault("min_messages_threshold", 50)
cfg.setdefault("basic", {}).setdefault("filter_bot_messages", True)
# 优先读本地消息库(learning_chat 落库),取不到再回退接口分页
cfg.setdefault("basic", {}).setdefault("use_local_history", True)
cfg.setdefault("analysis_features", {}).setdefault("chat_quality_analysis_enabled", False)
cfg.setdefault("incremental", {}).setdefault("incremental_enabled", False)
return cfg
def config_file() -> Path:
return get_data_dir(PLUGIN_NAME) / "config.json"
def load_config() -> ConfigDict:
cfg = ConfigDict(_default_config())
f = config_file()
if f.exists():
try:
user = json.loads(f.read_text(encoding="utf-8"))
for group, values in user.items():
if isinstance(values, dict):
cfg.setdefault(group, {}).update(values)
else:
cfg[group] = values
except (OSError, json.JSONDecodeError) as e:
_log_warn(f"读取配置文件失败: {e}")
# 环境变量/NoneBot .env 永远优先于持久化 config.json,避免被空值覆盖
cfg.setdefault("llm", {}).update(_llm_env_values())
return cfg
def save_config(cfg: dict) -> None:
try:
config_file().write_text(
json.dumps(cfg, ensure_ascii=False, indent=2), encoding="utf-8"
)
except OSError as e:
_log_warn(f"保存配置文件失败: {e}")
def _log_warn(msg: str) -> None:
try:
from .core.utils.logger import logger
logger.warning(msg)
except Exception:
pass