"""漫画源注册表(插件化)
源适配器为插件,需用户自行实现并放置于 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}")