From 210146af15e8f553f34c1c73e94d467a0126d267 Mon Sep 17 00:00:00 2001 From: AlexandreSenpai Date: Mon, 11 Nov 2024 02:25:44 -0300 Subject: [PATCH] feat: add google drive as storage option --- .vscode/settings.json | 3 +- enma/__init__.py | 1 + enma/domain/entities/manga.py | 2 + enma/infra/adapters/repositories/mangadex.py | 2 + enma/infra/adapters/repositories/manganato.py | 9 +- enma/infra/adapters/repositories/nhentai.py | 1 + enma/infra/adapters/storage/google_drive.py | 83 +++++++++++++++++++ requirements.txt | 3 + setup.cfg | 6 ++ 9 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 enma/infra/adapters/storage/google_drive.py diff --git a/.vscode/settings.json b/.vscode/settings.json index e4df47f..eef6889 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,5 +5,6 @@ "tests" ], "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, + "python.analysis.autoImportCompletions": true } diff --git a/enma/__init__.py b/enma/__init__.py index 4b25e27..d1da809 100644 --- a/enma/__init__.py +++ b/enma/__init__.py @@ -9,6 +9,7 @@ from enma.infra.adapters.downloaders.manganato import ManganatoDownloader from enma.application.core.interfaces.downloader_adapter import IDownloaderAdapter from enma.application.core.interfaces.saver_adapter import ISaverAdapter +from enma.infra.adapters.storage.google_drive import GoogleDriveStorage from enma.infra.adapters.storage.local import LocalStorage from enma.domain.entities.manga import Manga, Chapter, SymbolicLink from enma.domain.entities.search_result import SearchResult diff --git a/enma/domain/entities/manga.py b/enma/domain/entities/manga.py index 0ee18f6..47b214f 100644 --- a/enma/domain/entities/manga.py +++ b/enma/domain/entities/manga.py @@ -125,6 +125,7 @@ class ILanguage(TypedDict): class Manga(Entity[IMangaProps]): def __init__(self, title: Title, + status: Literal['ongoing', 'completed'], url: str, chapters: Union[list[Chapter], None] = None, language: Union[str, None] = None, @@ -141,6 +142,7 @@ def __init__(self, created_at=created_at, updated_at=updated_at) + self.status = status self.title = title self.language = language self.cover = cover diff --git a/enma/infra/adapters/repositories/mangadex.py b/enma/infra/adapters/repositories/mangadex.py index fd88cf5..a752f8e 100644 --- a/enma/infra/adapters/repositories/mangadex.py +++ b/enma/infra/adapters/repositories/mangadex.py @@ -345,12 +345,14 @@ def __parse_full_manga(self, thumbnail = self.__get_cover(manga_data.get('id'), manga_data.get('relationships')) + status = 'completed' if attrs.get('status', "").lower() == 'completed' else 'ongoing' manga = Manga(title=self.__get_title(alt_titles=attrs.get('altTitles'), title=attrs.get('title', dict()).get('en') or ''), id=manga_data.get('id'), created_at=datetime.fromisoformat(attrs.get('createdAt')), updated_at=datetime.fromisoformat(attrs.get('updatedAt')), + status=status, url=urljoin(self.__SITE_URL, f'title/{manga_data.get("id")}'), language=Language.get(attrs.get('originalLanguage').strip().lower().replace('-', '_'), 'unknown'), authors=self.__extract_authors(manga_data.get('relationships', list())), diff --git a/enma/infra/adapters/repositories/manganato.py b/enma/infra/adapters/repositories/manganato.py index 09aeded..e5d2c80 100644 --- a/enma/infra/adapters/repositories/manganato.py +++ b/enma/infra/adapters/repositories/manganato.py @@ -138,10 +138,12 @@ def get(self, if table_vals is None: return - title_cel, author_cel, _, genres_cel = table_vals + title_cel, author_cel, status_cel, genres_cel = table_vals title = self.__create_title(main_title=elem_title, alternative=title_cel.text) author = author_cel.text.strip() + status = status_cel.text.strip().lower() + genres = genres_cel.text.replace('\n', '').split(' - ') extra_infos = cast(Tag, soup.find('div', {'class': 'story-info-right-extent'})) @@ -164,6 +166,7 @@ def get(self, executor.shutdown() return Manga(title=title, + status='completed' if status == 'completed' else 'ongoing', authors=[Author(name=author)] if author is not None else None, genres=[Genre(name=genre_name) for genre_name in genres], url=urljoin(self.__BASE_URL, identifier), @@ -234,8 +237,8 @@ def paginate(self, page: int) -> Pagination: info = cast(Tag, item.find('a', {'class': 'genres-item-img bookmark_check'})) cover = info.find('img') pagination.results.append(Thumb(id=info['href'].split('/')[-1], # type: ignore - url=info['href'], - title=info['title'] if info is not None else "", + url=info['href'], # type: ignore + title=info['title'] if info is not None else "", # type: ignore cover=Image(uri=cover['src'], width=0, height=0))) # type: ignore last_pagination = soup.find('a', {'class': 'page-blue page-last'}) diff --git a/enma/infra/adapters/repositories/nhentai.py b/enma/infra/adapters/repositories/nhentai.py index d72ae21..c1c043b 100644 --- a/enma/infra/adapters/repositories/nhentai.py +++ b/enma/infra/adapters/repositories/nhentai.py @@ -246,6 +246,7 @@ def get(self, id=doujin.get('id'), created_at=datetime.fromtimestamp(doujin.get('upload_date'), tz=timezone.utc), updated_at=datetime.fromtimestamp(doujin.get('upload_date'), tz=timezone.utc), + status='completed', url=urljoin(self.__BASE_URL, f'g/{doujin.get("id")}'), language=language[0] if len(language) > 0 else None, authors=authors, diff --git a/enma/infra/adapters/storage/google_drive.py b/enma/infra/adapters/storage/google_drive.py new file mode 100644 index 0000000..93492ba --- /dev/null +++ b/enma/infra/adapters/storage/google_drive.py @@ -0,0 +1,83 @@ +try: + from googleapiclient.discovery import build, HttpError + from googleapiclient.http import MediaIoBaseUpload + from google.oauth2.service_account import Credentials +except ImportError as e: + raise ImportError( + "The dependencies for Google Drive are not installed. " + "Please install them using 'pip install enma[google_drive]'." + ) from e + +from enma.application.core.interfaces.saver_adapter import File, ISaverAdapter + +class GoogleDriveStorage(ISaverAdapter): + + def __init__( + self, + credentials_path: str, + root_shared_folder: str): + self.credentials = Credentials.from_service_account_file(credentials_path) + self.service = build('drive', 'v3', credentials=self.credentials) + self.root_shared_folder = root_shared_folder + + def save(self, path: str, file: File) -> bool: + try: + folder_id = self._get_or_create_folder(path) + + file_metadata = { + 'name': file.name, + 'parents': [folder_id] + } + + media = MediaIoBaseUpload(file.data, mimetype='application/octet-stream') + + teste = self.service.files().create( + body=file_metadata, + media_body=media, + fields='id' + ).execute() + print(teste) + return True + except HttpError as e: + print(f'Erro HTTP ao fazer upload: {e}') + return False + except Exception as e: + print(f'Erro ao fazer upload: {e}') + return False + + def _get_or_create_folder(self, path: str) -> str: + folder_names = path.strip('/').split('/') + parent_id = self.root_shared_folder + + for folder_name in folder_names: + query = ( + f"name = '{folder_name}' and " + f"mimeType = 'application/vnd.google-apps.folder' and " + f"'{parent_id}' in parents and " + f"trashed = false" + ) + + results = self.service.files().list( + q=query, + spaces='drive', + fields='files(id, name)' + ).execute() + items = results.get('files', []) + + if items: + folder_id = items[0]['id'] + else: + folder_metadata = { + 'name': folder_name, + 'mimeType': 'application/vnd.google-apps.folder', + 'parents': [parent_id] + } + folder = self.service.files().create( + body=folder_metadata, + fields='id' + ).execute() + folder_id = folder.get('id') + + parent_id = folder_id + + return parent_id \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 709ec85..026dd3c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,6 @@ pydantic==2.8.2 pytest==8.3.2 expiringdict==1.2.2 pytest-cov==4.1.0 +google-api-python-client>=2.0.0 +google-auth-httplib2>=0.1.0 +google-auth-oauthlib>=0.4.0 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 6b600aa..7302d44 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,6 +27,12 @@ setup_requires = [options.package_data] enma = *.txt, *.rst, *.md +[options.extras_require] +google_drive = + google-api-python-client>=2.0.0 + google-auth-httplib2>=0.1.0 + google-auth-oauthlib>=0.4.0 + [tool.setuptools_scm] write_to = enma/_version.py