Initial commit: doujinshi-dl generic plugin framework

History reset as part of DMCA compliance. The project has been
refactored into a generic, site-agnostic download framework.
Site-specific logic lives in separate plugin packages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ricter Zheng
2026-03-25 10:37:21 +08:00
commit 81d008036a
53 changed files with 4063 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
# coding: utf-8
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import List, Dict, Any, Iterator, Tuple
@dataclass
class GalleryMeta:
id: str
name: str
pretty_name: str
img_id: str
ext: list
pages: int
info: Dict[str, Any] = field(default_factory=dict)
extra: Dict[str, Any] = field(default_factory=dict) # plugin-private data
def to_dict(self) -> dict:
d = {
'id': self.id,
'name': self.name,
'pretty_name': self.pretty_name,
'img_id': self.img_id,
'ext': self.ext,
'pages': self.pages,
}
d.update(self.info)
d.update(self.extra)
return d
class BaseParser(ABC):
@abstractmethod
def fetch(self, gallery_id: str) -> GalleryMeta: ...
@abstractmethod
def search(self, keyword: str, sorting: str = 'date', page=None, **kwargs) -> List[Dict]: ...
def favorites(self, page=None) -> List[Dict]:
return []
def configure(self, args): ...
class BaseModel(ABC):
@abstractmethod
def iter_tasks(self) -> Iterator[Tuple[str, str]]: ...
# yields (url, filename) tuples
class BaseSerializer(ABC):
@abstractmethod
def write_all(self, meta: GalleryMeta, output_dir: str): ...
def finalize(self, output_dir: str) -> None:
pass
class BasePlugin(ABC):
name: str
@abstractmethod
def create_parser(self) -> BaseParser: ...
@abstractmethod
def create_model(self, meta: GalleryMeta, name_format: str = '[%i][%a][%t]') -> BaseModel: ...
@abstractmethod
def create_serializer(self) -> BaseSerializer: ...
def register_args(self, argparser): pass
def check_auth(self) -> None:
pass
def print_results(self, results) -> None:
pass