"""微信公众号(wechat_mp)渠道插件入口。 实现 ``CHANNEL_ENTRY`` 契约,注册 11 个适配器与生命周期钩子,返回 ``PluginManifest``。由宿主在 loaded 阶段通过 ``importlib`` 导入并调用。 调用顺序(严格,违反顺序将触发 ``PermissionDeniedError``): 1. 获取端口实例(仅在 manifest ``accessible_ports`` 声明集合内取用, 宿主在 ``PluginHostImpl._checkPortAccess`` 中校验)。 2. 实例化 ``WeChatMpClient`` 与 ``WeChatMpLifecycleHandler``。 3. 注册 11 个适配器(inbound / outbound / rich_message / session / status / lifecycle / probeable / doctor / wizard / whitelist / directory)。 4. 注册生命周期钩子(鸭子类型调用)。 5. 返回 ``PluginManifest``(能力边界由 manifest 字段静态声明,宿主在加载 期通过 ``PluginCapabilityChecker.check`` 校验)。 F-03 单真相源:本入口仅承接 discover 阶段解析的 ``ChannelManifest``, 不再重复构造声明性字段(capabilities / config_schema / accessible_ports / injectable_pipelines / resource_quota / env_vars / credential_strategy 等), 所有声明性字段以 ``manifest.json`` 为唯一真相源。本函数仅负责适配器实例化、 client 实例化、LifecycleHandler 构造与 ``registerAdapter`` 调用。 适配器 DI 说明: - ``inbound`` 接收 ``(client, config_port, logger_port)``:需从 ConfigPort 读取 ``encrypt_mode`` / ``encoding_aes_key`` / ``token`` 用于入站消息 解密与签名校验。 - ``outbound`` 接收 ``(client, config_port, logger_port)``:需从 ConfigPort CHANNEL 作用域读取 ``max_message_length`` 用于长文本分片。 - ``rich_message`` 接收 ``(logger_port)``:纯协议转换,将 ``RichMessage`` DTO 渲染为微信公众号 news 图文消息 JSON。 - ``session`` / ``status`` / ``wizard`` 接收 ``(logger_port)``:仅做协议 转换,所有数据从 raw_event.payload 或入参提取。 - ``lifecycle`` 接收 ``(client, config_port, cache_port, logger_port)``: 账户配置写入后触发 access_token 预取,删除/禁用时清理 CachePort 缓存。 - ``probeable`` 接收 ``(client, persistence_port, logger_port)``:需动态 发现 account_id 调用 ``get_callback_ip`` 探活(契约 ``probe()`` 无参)。 - ``doctor`` 接收 ``(client, logger_port)``:调用 ``get_callback_ip`` / ``get_user_list`` 执行诊断。 - ``whitelist`` 接收 ``(cache_port, logger_port)``:白名单数据本地存储于 CachePort,不调用微信 API。 - ``directory`` 接收 ``(client, cache_port, logger_port)``:调用 ``get_user_list`` / ``get_user_info`` 查询关注者,用户信息缓存于 CachePort。 传输管理:wechat_mp 通过 ``TransportManager`` 的 ``both`` 模式管理消息 接收(manifest ``transport_mode: "both"``): - ``WebhookWorker``(优先):接收微信服务器 Webhook 推送(消息/事件), 端到端延迟 ≤5s(5 秒被动回复策略) - ``PullerWorker``(降级):Webhook 不可用时降级为无轮询(公众号无拉取 API,此模式实际不可用,仅保留契约一致性) 两者由传输引擎通过 ``ChannelAccountOnline`` / ``ChannelAccountOffline`` 领域事件触发启停,``LifecycleAdapter`` 与 ``LifecycleHandler`` 不自管理 传输任务(决策 3)。 依赖方向:仅 import ``yuxi.channels.contract.*`` + 标准库 + 同插件内部模块, 不污染框架层。 """ from __future__ import annotations from yuxi.channels.contract.plugin.entry import PluginHost from yuxi.channels.contract.plugin.manifest import ( ChannelManifest, PluginManifest, ) from ._constants import DEFAULT_MAX_CONNECTIONS from .adapters.directory_adapter import WeChatMpDirectoryAdapter from .adapters.doctor_adapter import WeChatMpDoctorAdapter from .adapters.inbound_adapter import WeChatMpInboundAdapter from .adapters.lifecycle_adapter import WeChatMpLifecycleAdapter from .adapters.outbound_adapter import WeChatMpOutboundAdapter from .adapters.probeable_adapter import WeChatMpProbeableAdapter from .adapters.rich_message_adapter import WeChatMpRichMessageAdapter from .adapters.session_adapter import WeChatMpSessionAdapter from .adapters.status_adapter import WeChatMpStatusAdapter from .adapters.whitelist_adapter import WeChatMpWhitelistAdapter from .adapters.wizard_adapter import WeChatMpWizardAdapter from .lifecycle import WeChatMpLifecycleHandler from .wechat_mp_client import WeChatMpClient # 已注册适配器类型列表(与 registerAdapter 调用顺序一致)。 # 此处为运行时适配器清单,非声明性字段,需与下方 registerAdapter 调用保持一致。 _ADAPTER_TYPES: tuple[str, ...] = ( "inbound", "outbound", "rich_message", "session", "status", "lifecycle", "probeable", "doctor", "wizard", "whitelist", "directory", ) def channel_entry(host: PluginHost, manifest: ChannelManifest) -> PluginManifest: """微信公众号 wechat_mp 渠道插件入口。 由宿主在 loaded 阶段调用,完成端口获取、客户端与生命周期钩子实例化、 适配器注册,返回 ``PluginManifest``。能力边界(accessible_ports / injectable_pipelines / resource_quota / config_schema / env_vars / credential_strategy 等)由 discover 阶段解析的 ``manifest`` 静态字段 承载,宿主在加载期通过 ``PluginCapabilityChecker.check`` 校验,本函数 不重复构造声明性字段(F-03 单真相源)。 参数: host: 插件宿主,提供端口获取与扩展点注册 API。 manifest: 渠道清单,由 discover 阶段从 ``manifest.json`` 解析得到, 承载所有声明性字段。本函数复用其 ``config_schema`` 注入 ``LifecycleHandler``,并直接作为返回 ``PluginManifest.manifest``。 """ # 1. 获取端口实例(仅在 manifest accessible_ports 声明集合内取用) # WeChatMpClient 依赖 ConfigPort/CachePort/LoggerPort(app_id/app_secret # 由 CredentialService 通过 ConfigPort→CachePort 管理); # PersistencePort 供 ProbeableAdapter 动态发现 account_id。 config_port = host.getConfigPort() logger_port = host.getLoggerPort() cache_port = host.getCachePort() persistence_port = host.getPersistencePort() # 2. 实例化 WeChatMpClient 与 LifecycleHandler # 依赖关系为单向:handler 在 onInit 调 client.attach_http_client 注入 # HTTP 连接池,client 不引用 handler。先创建 client,再创建 handler # 并通过构造器注入。config_schema 取自 manifest(F-03 单真相源)。 client = WeChatMpClient( config_port, cache_port, logger_port, plugin_version=manifest.version, ) handler = WeChatMpLifecycleHandler( config_port, logger_port, cache_port, manifest.config_schema, client, max_connections=( manifest.resource_quota.max_connections if manifest.resource_quota is not None else DEFAULT_MAX_CONNECTIONS ), persistence_port=persistence_port, ) # 3. 注册 11 个适配器 # inbound/outbound 接收 (client, config_port, logger_port); # rich_message 接收 (logger_port); # session/status 无构造参数(INV-5 无状态); # lifecycle 接收 (client, config_port, cache_port, logger_port); # probeable 接收 (client, persistence_port, logger_port); # doctor 接收 (client, config_port, cache_port, logger_port); # wizard 接收 (client, logger_port); # whitelist 接收 (cache_port, logger_port); # directory 接收 (client, cache_port, logger_port)。 host.registerAdapter("inbound", WeChatMpInboundAdapter(client, config_port, logger_port)) host.registerAdapter("outbound", WeChatMpOutboundAdapter(client, config_port, logger_port)) host.registerAdapter("rich_message", WeChatMpRichMessageAdapter(logger_port)) host.registerAdapter("session", WeChatMpSessionAdapter()) host.registerAdapter("status", WeChatMpStatusAdapter()) host.registerAdapter("lifecycle", WeChatMpLifecycleAdapter(client, config_port, cache_port, logger_port)) host.registerAdapter("probeable", WeChatMpProbeableAdapter(client, persistence_port, logger_port)) host.registerAdapter("doctor", WeChatMpDoctorAdapter(client, config_port, cache_port, logger_port)) host.registerAdapter("wizard", WeChatMpWizardAdapter(client, logger_port)) host.registerAdapter("whitelist", WeChatMpWhitelistAdapter(cache_port, logger_port)) host.registerAdapter("directory", WeChatMpDirectoryAdapter(client, cache_port, logger_port)) # 4. 注册生命周期钩子 # PluginHost Protocol 未声明 registerLifecycleHandler,但 PluginHostImpl # 已实现,运行时通过鸭子类型调用。 host.registerLifecycleHandler(handler) # 5. 返回 PluginManifest(manifest 直接复用入参,adapters 为运行时清单) return PluginManifest( manifest=manifest, adapters=_ADAPTER_TYPES, ) CHANNEL_ENTRY = channel_entry