"""漫画源注册表(插件化) 源适配器为插件,需用户自行实现并放置于 sources/ 目录下。 插件模块应继承 BaseSource 并在模块顶层调用 register_source() 注册。 启动时自动扫描 sources/ 下所有 .py 文件(排除 base.py)并尝试加载。 """ import importlib import logging from pathlib import Path from kobo_manga.sources.base import BaseSource logger = logging.getLogger(__name__) _registry: dict[str, type[BaseSource]] = {} def register_source(cls: type[BaseSource]) -> type[BaseSource]: """注册源适配器类。""" _registry[cls.name] = cls return cls def get_source(name: str) -> BaseSource: """按名字实例化源。""" _ensure_registered() if name not in _registry: available = ", ".join(_registry.keys()) or "(无已安装的源插件)" raise ValueError(f"未知源 '{name}',可用: {available}") return _registry[name]() def list_sources() -> list[str]: """返回所有已注册的源名称。""" _ensure_registered() return list(_registry.keys()) def infer_source_from_url(url: str) -> str | None: """从 URL 推断源名称。""" _ensure_registered() for name, cls in _registry.items(): if hasattr(cls, "URL_PATTERNS"): for pattern in cls.URL_PATTERNS: if pattern in url: return name return None _registered = False def _ensure_registered(): """自动扫描 sources/ 目录下的插件模块并注册。""" global _registered if _registered: return _registered = True sources_dir = Path(__file__).parent skip = {"__init__.py", "base.py"} for py_file in sorted(sources_dir.glob("*.py")): if py_file.name in skip: continue module_name = f"kobo_manga.sources.{py_file.stem}" try: importlib.import_module(module_name) except Exception as e: logger.debug(f"跳过源插件 {py_file.name}: {e}")