Skip to content

Architecture

Lingchu Bot is an application-side bot project based on NoneBot2. Current code splits the core plugin, command handling, configuration, storage, and database helpers under src/plugins/nonebot_plugin_lingchu_bot.

  • pyproject.toml: declares plugin directories, dependencies, adapters, and NoneBot plugin configuration, and is the current source for local plugin loading configuration.
  • Dockerfile / docker-compose.yml: container runtime entrypoints; the build generates the runtime /tmp/bot.py with nb-cli.
  • nonebot_plugin_lingchu_bot: core plugin package. It declares plugin metadata and loads shared capabilities.
  • Directorysrc/plugins/nonebot_plugin_lingchu_bot/
    • Directorycore/
      • async_utils.py
      • bot_state.py
      • config.py
      • handle_config_manager.py
      • http_security.py
      • menu_config.py
      • mutable_settings.py
      • schemas.py
      • Directoryhandle_config_defaults/
      • Directorysubplugins/
        • Directoryllm_chat/
        • Directorynovelai_image/
    • Directorydatabase/
      • _dialect_compat.py
      • Directorymodels/
      • Directoryorm_crud/
      • Directorytoml_store/
    • Directoryhandle/
      • menu.py
      • Directoryqq/
        • Directorycommands/
          • triggers.py
          • mute.py
          • member.py
          • block.py
          • announcement.py
          • remote.py
        • Directoryadapters/
          • Directoryonebot11/
            • Directorydefault/
            • Directorynapcat/
    • Directoryhooks/
      • adapters.py
      • interfaces.py
      • Directoryhandlers/
    • Directoryi18n/
      • init .py
      • babel.cfg
      • Directorylocales/
        • Directoryen_US/
        • Directoryzh_CN/
    • Directorymigrations/
      • 30f5a01259cd_initial_schema.py
      • a1b2c3d4e5f6_permissions_schema.py
      • b7c8d9e0f1a2_event_policy_partitions.py
      • c3d4e5f6a7b8_scheduler_jobs.py
      • cf2c06d51a17_blocklist_unique_constraint.py
    • Directorypermissions/
      • init .py
      • admin.py
      • bootstrap.py
      • config.py
      • platforms.py
      • service.py
      • subject_policy.py
      • types.py
    • Directoryplatforms/
      • registry.py
      • Directoryqq/
        • permissions.py
    • Directoryrepositories/
      • init .py
      • blocklist.py
      • message_store.py
      • permissions.py
      • registry.py
      • scheduler_jobs.py
    • Directoryservices/
      • message_store.py
      • protocol_restart_feedback.py
      • scheduler.py
      • Directoryllm/
    • Directorystart/
      • startup.py
  • Directoryapps/docs/
    • Directorycontent/
    • Directorysrc/
  • Directoryapps/lingc-cli/
    • Directorysrc/
    • Directorytests/
  • pyproject.toml
  • Taskfile.yml
  • turbo.json
  • Dockerfile
Module Responsibility
core/config.py Deployment-only Config pydantic model, resolved by NoneBot get_plugin_config()
core/mutable_settings.py Typed localstore repository for online-editable MutableRuntimeSettings
core/handle_config_manager.py HandleConfigManager — pydantic-validated per-command TOML configs
core/handle_config_defaults/ Per-command pydantic BaseModel defaults registered via register_handle_defaults()
core/schemas.py Schema definitions and explicit installation support for CLI tooling
core/bot_state.py bot_state.toml persistence backed by BotStateFile(BaseModel)
core/subplugins/ Subplugin contracts, loader, and bundled subplugins (llm_chat, novelai_image)
database/toml_store/ TOML file storage package
database/models/ ORM models package (message, blocklist, registry, identity)
database/orm_crud/ Async CRUD helpers (session-first signature; see Storage and ORM)
migrations/ Alembic database migrations
handle/menu.py Menu system (pages, sections, features, availability)
handle/qq/commands/ Shared QQ command definitions (Alconna matchers, triggers)
handle/qq/adapters/onebot11/ OneBot V11 handlers (default, napcat)
hooks/ NoneBot event lifecycle handlers (lifecycle, bot_connection, message_store, api_audit)
i18n/__init__.py gettext/Babel translation helpers, locale reading, and async catalog warmup
platforms/registry.py Platform capabilities, adapter mapping, priority selection, and conflict validation
platforms/qq/permissions.py QQ default identity groups and runtime platform identity resolution
permissions/ UID identity, platform account, group membership, command grant, and SUPERUSERS APIs
repositories/blocklist.py Blocklist data access
repositories/message_store.py Message store repository
repositories/permissions.py Permission-system ORM repository
services/message_store.py Message event, processing result, bot lifecycle, and platform API call summary recording
services/llm/ LLM service abstraction (backends, capabilities, runtime, security)
start/ Startup hooks

The plugin declares adapters by platform capability instead of treating every imported concrete adapter as enabled. The currently implemented profile is QQ, with only ~onebot.v11 active in the startup flow. Other adapter IDs (such as ~milky, ~qq, or ~onebot.v12) are not implemented; configuring any of them exits with a clear PlatformAdapterUnknownError. When unset, the default is OneBot V11.

~onebot.v11 active ~milky~qq~onebot.v12 removed

Explicitly configuring multiple known adapters for the same platform raises PlatformAdapterConflictError. Lingchu Bot does not control which adapters NoneBot actually imports or registers; it only checks that the selected adapter has been loaded and registered by NoneBot. When unset, Lingchu selects ~onebot.v11 by default and raises PlatformAdapterNotLoadedError if OneBot V11 is not loaded. Other same-platform adapters may still be imported or registered, but Lingchu treats them as disabled and excludes them from platform resolution, message storage, and API call recording. Explicitly declaring an adapter that Lingchu has not implemented or cannot recognize in LINGCHUAdapter fails startup. Extra unknown adapters seen at runtime are not considered enabled by is_adapter_enabled(); message storage groups them under the stable unknown platform. Other platform profiles and non-group-management capabilities depend on later implementation and tests.

Platform permission modules are now discovered through the PlatformProfile.permission_module field in the adapter registry, eliminating hardcoded module paths. This unifies platform identifier management in a single location.

The project prefers NoneBot plugin configuration, localstore, and ORM plugin capabilities instead of hard-coded paths or database connections in business code.

Potentially blocking paths such as file storage, TOML parsing, deepcopy, command parsing, and translation catalog loading are moved to worker threads to avoid blocking the NoneBot event loop.

Plugin startup does not create deployment configuration files or install JSON Schema files. Generate editor schemas manually with pydantic’s model_json_schema(), or hand them to NoneBot configuration management.

Lingchu Bot uses pydantic as the source of truth for deployment, mutable runtime, handle, and bot-state configuration. NoneBot resolves deployment fields through get_plugin_config(Config); online-editable settings use a separate typed localstore repository.

Surface Pydantic model Notes
Plugin env / .env core/config.py::Config (extends DeploymentSettings) NoneBot get_plugin_config() is the only deployment-field resolver.
Online-editable settings _lingchu_bot_contracts::MutableRuntimeSettings Persisted by core/mutable_settings.py in localstore-owned runtime-overrides.toml.
Per-command handle config core/handle_config_defaults/<command>.py::<Model>(BaseModel) Registered via register_handle_defaults(); HANDLE_DEFAULTS_REGISTRY is dict[str, type[BaseModel]]. HandleConfigManager.get_config() / update_config() use type_validate_python(model_cls, toml_dict). HandleConfig dataclass still holds dict[str, Any] to preserve the frozen-dataclass interface; _build_handle_config bridges the pydantic ↔ dict boundary via model_dump(mode="json").
Bot state core/bot_state.py::BotStateFile(BaseModel) Uses Field(alias="global") + populate_by_name=True to bridge the global Python keyword; _save_bot_state() serializes with model_dump(mode="json", by_alias=True).
JSON Schema files Generated manually with pydantic model_json_schema() pydantic owns JSON Schema emission; startup never installs them.
  1. Declare a pydantic.BaseModel subclass in core/handle_config_defaults/<command>.py.
  2. Register it via register_handle_defaults("<command_key>", <Model>).
  3. Run task db:check; regenerate editor schemas via pydantic when they are needed.
  4. The corresponding <command_key>.toml is created on first read; users can edit it and HandleConfigManager.get_config(<command_key>) will validate through pydantic.

Do not return bare dict defaults — pydantic owns validation and JSON Schema emission. HandleConfigManager.validate_config() is removed; pydantic raises on invalid input.