diff --git a/.gitignore b/.gitignore index 5dab794..006f9b5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ workpath_pyinstaller/ hd_cards_downloader_tracker hd_fields_downloader_tracker +HdDownloader.LICENSE.txt diff --git a/HdDownloader.LICENSE.txt b/HdDownloader.LICENSE.txt deleted file mode 100644 index 4d7e4e9..0000000 --- a/HdDownloader.LICENSE.txt +++ /dev/null @@ -1,19 +0,0 @@ -Copyright © 2021 Douglas Sebastian - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the “Software”), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/Makefile b/Makefile index d748017..1ac5df3 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,19 @@ -FILENAME = EDOPro-HD-Downloader -LICENSE = HdDownloader.LICENSE.txt -DISTPATH = bin -WORKPATH = workpath_pyinstaller -ZIP_WIN = windows.zip +FILENAME = EDOPro-HD-Downloader +LICENSE = LICENSE +LICENSE_BIN = HdDownloader.LICENSE.txt +DISTPATH = bin +WORKPATH = workpath_pyinstaller +ZIP_WIN = windows.zip build: pyinstaller main.py -y --distpath "$(DISTPATH)" -F --specpath "$(DISTPATH)" -n "$(FILENAME)" -c --clean --workpath "$(WORKPATH)" - cp "$(LICENSE)" "$(DISTPATH)/$(LICENSE)" + cp "$(LICENSE)" "$(DISTPATH)/$(LICENSE_BIN)" rm -rf "$(WORKPATH)" 7z-win: build - cd "$(DISTPATH)" && 7z a "$(ZIP_WIN)" "$(FILENAME).exe" "$(LICENSE)" + cd "$(DISTPATH)" && 7z a "$(ZIP_WIN)" "$(FILENAME).exe" "$(LICENSE_BIN)" + +clean: + rm -rf "hd_cards_downloader_tracker" + rm -rf "hd_fields_downloader_tracker" diff --git a/apiaccess.py b/apiaccess.py deleted file mode 100644 index e25c23a..0000000 --- a/apiaccess.py +++ /dev/null @@ -1,52 +0,0 @@ -from http.client import HTTPResponse as __HTTPResponse -from urllib import request as __request -from json import loads as __loads - -API_URL = "https://db.ygoprodeck.com/api/v7/cardinfo.php" -"""Base API URL for YGOProDeck""" - -API_HEADER = { - "User-Agent": "NiiMiyo-EDOPro-HD-Downloader/2.0.1" -} -"""Header JSON to be used in an API request""" - -def __get_ids_from_api_response(response: __HTTPResponse) -> list[int]: - """Returns only the ids of the cards requested""" - - data = __loads(response.read()).get("data") - ids: list[int] = list() - - for card in data: - for image in card.get("card_images"): - ids.append(image.get("id")) - - return ids - - -def get_all_cards() -> list[int]: - """Returns the ids of all Yu-Gi-Oh! cards in - `db.ygoprodeck.com` database""" - - try: - request = __request.Request(API_URL, headers=API_HEADER) - response = __request.urlopen(request) - except Exception as e: - print(f"Error fetching db.ygoprodeck.com: {e}") - return list() - else: - return __get_ids_from_api_response(response) - - -def get_all_fields() -> list[int]: - """Returns the ids of all Yu-Gi-Oh! Field Spell cards in - `db.ygoprodeck.com` database""" - - try: - params = r"?type=spell%20card&race=field" - request = __request.Request(API_URL + params, headers=API_HEADER) - response = __request.urlopen(request) - except Exception as e: - print(e) - return list() - else: - return __get_ids_from_api_response(response) diff --git a/commands/cmd_all.py b/commands/cmd_all.py index 8a65144..d399c93 100644 --- a/commands/cmd_all.py +++ b/commands/cmd_all.py @@ -9,8 +9,8 @@ def __cmd_all(_: str) -> CommandReturn: return allcards.action(_) + allfields.action(_) # type: ignore -CommandHandler.add_command(DownloaderCommand( +COMMAND_ALL = DownloaderCommand( name="all", help_text="downloads all cards images and all fields artworks", action=__cmd_all -)) +) diff --git a/commands/cmd_allcards.py b/commands/cmd_allcards.py index 1e64ddf..263818c 100644 --- a/commands/cmd_allcards.py +++ b/commands/cmd_allcards.py @@ -1,6 +1,5 @@ -from command_handler import CommandHandler from commands.typing import CommandReturn, DownloadCard, DownloaderCommand -from apiaccess import get_all_cards +from web_access.ygoprodeck_api import get_all_cards def __cmd_all_cards_action(_: str) -> CommandReturn: @@ -10,8 +9,8 @@ def __cmd_all_cards_action(_: str) -> CommandReturn: ] -CommandHandler.add_command(DownloaderCommand( +COMMAND_ALLCARDS = DownloaderCommand( name="allcards", help_text="downloads all cards", action=__cmd_all_cards_action -)) +) diff --git a/commands/cmd_allfields.py b/commands/cmd_allfields.py index 2653faf..5510122 100644 --- a/commands/cmd_allfields.py +++ b/commands/cmd_allfields.py @@ -1,7 +1,5 @@ -from command_handler import CommandHandler from commands.typing import DownloadCard, DownloaderCommand, CommandReturn -from apiaccess import get_all_fields - +from web_access.ygoprodeck_api import get_all_fields def __cmd_all_fields_action(_: str) -> CommandReturn: return [ @@ -10,8 +8,8 @@ def __cmd_all_fields_action(_: str) -> CommandReturn: ] -CommandHandler.add_command(DownloaderCommand( +COMMAND_ALLFIELDS = DownloaderCommand( name="allfields", help_text="downloads all fields artworks", action=__cmd_all_fields_action -)) +) \ No newline at end of file diff --git a/commands/cmd_exit.py b/commands/cmd_exit.py index c5ca167..7ddc041 100644 --- a/commands/cmd_exit.py +++ b/commands/cmd_exit.py @@ -1,4 +1,3 @@ -from command_handler import CommandHandler from commands.typing import CommandReturn, DownloaderCommand @@ -7,8 +6,8 @@ def __cmd_exit_action(_: str) -> CommandReturn: exit(0) -CommandHandler.add_command(DownloaderCommand( +COMMAND_EXIT = DownloaderCommand( name="exit", help_text="closes the program", action=__cmd_exit_action -)) +) diff --git a/commands/cmd_force.py b/commands/cmd_force.py index d37a430..e932135 100644 --- a/commands/cmd_force.py +++ b/commands/cmd_force.py @@ -1,4 +1,3 @@ -from command_handler import CommandHandler from commands.typing import CommandReturn, DownloadCard, DownloaderCommand from commands.utils import get_args from input_handler import handle_input @@ -14,9 +13,9 @@ def __cmd_force_action(user_input: str) -> CommandReturn: for c in cards ] -CommandHandler.add_command(DownloaderCommand( +COMMAND_FORCE = DownloaderCommand( name="force", shown_name="force ", help_text="executes ignoring trackers (example: /force /allcards)", action=__cmd_force_action -)) +) diff --git a/commands/cmd_help.py b/commands/cmd_help.py index ee71291..7f28782 100644 --- a/commands/cmd_help.py +++ b/commands/cmd_help.py @@ -25,8 +25,8 @@ def __cmd_help_action(_: str) -> CommandReturn: return [] -CommandHandler.add_command(DownloaderCommand( +COMMAND_HELP = DownloaderCommand( name="help", help_text="see this text", action=__cmd_help_action -)) +) diff --git a/commands/setup.py b/commands/setup.py index 35a2b73..eb53d44 100644 --- a/commands/setup.py +++ b/commands/setup.py @@ -1,9 +1,16 @@ -# Yes, this is correct -# I'm sorry +from command_handler import CommandHandler +from commands.cmd_allcards import COMMAND_ALLCARDS +from commands.cmd_all import COMMAND_ALL +from commands.cmd_allfields import COMMAND_ALLFIELDS +from commands.cmd_exit import COMMAND_EXIT +from commands.cmd_force import COMMAND_FORCE +from commands.cmd_help import COMMAND_HELP + + def setup_commands(): - import commands.cmd_allcards as _ - import commands.cmd_allfields as _ - import commands.cmd_exit as _ - import commands.cmd_force as _ - import commands.cmd_help as _ - import commands.cmd_all as _ + CommandHandler.add_command(COMMAND_ALL) + CommandHandler.add_command(COMMAND_ALLCARDS) + CommandHandler.add_command(COMMAND_ALLFIELDS) + CommandHandler.add_command(COMMAND_EXIT) + CommandHandler.add_command(COMMAND_FORCE) + CommandHandler.add_command(COMMAND_HELP) diff --git a/constants.py b/constants.py new file mode 100644 index 0000000..ca1de0d --- /dev/null +++ b/constants.py @@ -0,0 +1,28 @@ +DOWNLOADER_VERSION = "2.1" +"""Program version""" + +REQUEST_HEADERS = { + "User-Agent": f"NiiMiyo-EDOPro-HD-Downloader/{DOWNLOADER_VERSION}" +} +"""Header to be used in an HTTP request""" + +INPUT_STRING = "Insert deck name (without .ydk) or command: " +"""String that appears at user input""" + +YGOPRODECK_CARDS_URL = "https://db.ygoprodeck.com/api/v7/cardinfo.php" +"""Base API URL for YGOProDeck""" + +IMAGES_BASE_URL = "https://images.ygoprodeck.com/images/cards" +"""Base URL for images""" + +CARD_CACHE_PATH = "./hd_cards_downloader_tracker" +"""Path to the cards cache file""" + +FIELD_CACHE_PATH = "./hd_fields_downloader_tracker" +"""Path to the fields cache file""" + +SETUP_CREATION_FILES = (CARD_CACHE_PATH, FIELD_CACHE_PATH) +"""Files needed on setup""" + +SETUP_CREATION_FOLDERS = ("pics", "pics/field") +"""Folders needed on setup""" diff --git a/downloader.py b/downloader.py deleted file mode 100644 index 4cf751b..0000000 --- a/downloader.py +++ /dev/null @@ -1,26 +0,0 @@ -from urllib import request as __request -from os.path import join as __join - -from commands.typing import DownloadCard - -def download_image(card: DownloadCard) -> bool: - """Downloads the card image or artwork and puts in the specified folder. - - Returns `True` if downloads successfully, otherwise returns `False`.""" - - img_url = "https://storage.googleapis.com/ygoprodeck.com/pics" - if not card.artwork: - img_url += f"/{card.card_id}.jpg" - store_at = "./pics/" - else: - img_url += f"_artgame/{card.card_id}.jpg" - store_at = "./pics/field/" - - file_path = __join(store_at, f"{card.card_id}.jpg") - try: - __request.urlretrieve(img_url, file_path) - return True - - except Exception as e: - print(f"Error downloading '{card.card_id}': {e}") - return False diff --git a/main.py b/main.py index 49cbc2c..1f843e9 100644 --- a/main.py +++ b/main.py @@ -2,30 +2,29 @@ from time import sleep from traceback import print_exc from commands.setup import setup_commands +from constants import DOWNLOADER_VERSION, INPUT_STRING, SETUP_CREATION_FILES from input_handler import handle_input from commands.typing import DownloadCard -from downloader import download_image -from tracker import (already_downloaded, card_cache_path, field_cache_path, - mark_as_downloaded) +from web_access.downloader import download_image +from tracker import (already_downloaded, mark_as_downloaded) -# String that appears at user input -INPUT_STRING = "Insert deck name (without .ydk) or command: " def initialize(): """Creates tracker files if they do not exist, setups all commands and introduces the program """ - global card_cache_path, field_cache_path - for i in card_cache_path, field_cache_path: - if not exists(i): - open(i, "w+").close() + for f in SETUP_CREATION_FILES: + if not exists(f): + with open(f, "w+"): + # I only need that the files exist + pass setup_commands() print("\n".join([ - "EDOPro HD Downloader", + f"EDOPro HD Downloader v{DOWNLOADER_VERSION}", "Created by Nii Miyo", "Type \"/help\" for help" ])) @@ -37,7 +36,7 @@ def to_download(card: DownloadCard): if (card.force) or (not already_downloaded(card)): success = download_image(card) if success: mark_as_downloaded(card) - sleep(.1) + sleep(.5) def main(): diff --git a/tracker.py b/tracker.py index 33f47d7..bcf75c0 100644 --- a/tracker.py +++ b/tracker.py @@ -1,17 +1,14 @@ from commands.typing import DownloadCard +from constants import CARD_CACHE_PATH, FIELD_CACHE_PATH -card_cache_path = "./hd_cards_downloader_tracker" -field_cache_path = "./hd_fields_downloader_tracker" - def __get_cached(is_artwork: bool): """Reads tracker file and returns a list of ids that already were downloaded""" - cache = field_cache_path if is_artwork else card_cache_path - cache_file = open(cache, mode="r+", encoding="utf8") - cards = [c.strip() for c in cache_file.readlines()] - cache_file.close() + cache = FIELD_CACHE_PATH if is_artwork else CARD_CACHE_PATH + with open(cache, mode="r+", encoding="utf8") as cache_file: + cards = [c.strip() for c in cache_file.readlines()] return cards def already_downloaded(card: DownloadCard): @@ -24,8 +21,7 @@ def already_downloaded(card: DownloadCard): def mark_as_downloaded(card: DownloadCard): """Opens tracker file to add an id to the downloaded list""" - cache = card_cache_path if not card.artwork else field_cache_path + cache = FIELD_CACHE_PATH if card.artwork else CARD_CACHE_PATH - cache_file = open(cache, mode="a+", encoding="utf8") - cache_file.write(f"{card.card_id}\n") - cache_file.close() + with open(cache, mode="a+", encoding="utf8") as cache_file: + cache_file.write(f"{card.card_id}\n") diff --git a/web_access/downloader.py b/web_access/downloader.py new file mode 100644 index 0000000..ca40ac6 --- /dev/null +++ b/web_access/downloader.py @@ -0,0 +1,30 @@ +import requests +from os.path import join + +from commands.typing import DownloadCard +from constants import IMAGES_BASE_URL + +def download_image(card: DownloadCard) -> bool: + """ + Downloads the card image or artwork and puts in the specified folder. + + Returns `True` if downloads successfully, otherwise returns `False`. + """ + + url = IMAGES_BASE_URL + store_at = "./pics/" + + if card.artwork: + url += "_cropped" + store_at += "field/" + url += f"/{card.card_id}.jpg" + + file_path = join(store_at, f"{card.card_id}.jpg") + try: + res = requests.get(url) + with open(file_path, 'wb+') as f: + f.write(res.content) + return True + except Exception as e: + print(f"Error downloading '{card.card_id}': {type(e).__name__}\n{e}") + return False diff --git a/web_access/request_helper.py b/web_access/request_helper.py new file mode 100644 index 0000000..de8175b --- /dev/null +++ b/web_access/request_helper.py @@ -0,0 +1,10 @@ +from typing import Any, Optional +import requests +from constants import REQUEST_HEADERS + +def make_request( + url: str, + params: Optional[dict[Any, Any]] = None +) -> requests.Response: + + return requests.get(url, headers=REQUEST_HEADERS, params=params) diff --git a/web_access/ygoprodeck_api.py b/web_access/ygoprodeck_api.py new file mode 100644 index 0000000..dde0be1 --- /dev/null +++ b/web_access/ygoprodeck_api.py @@ -0,0 +1,42 @@ +from requests import Response +from constants import YGOPRODECK_CARDS_URL +from web_access.request_helper import make_request + + +def _get_ids_from_response(response: Response) -> list[int]: + """Returns only the ids of the cards requested""" + data = response.json().get("data") + ids: list[int] = list() + + for c in data: + for img in c.get("card_images"): + ids.append(img.get("id")) + return ids + +def get_all_cards() -> list[int]: + """Returns the ids of all Yu-Gi-Oh! cards in `db.ygoprodeck.com` database""" + + try: + response = make_request(YGOPRODECK_CARDS_URL) + return _get_ids_from_response(response) + except Exception as e: + print(f"Error fetching db.ygoprodeck.com: {type(e).__name__}\n{e}") + + return list() + +def get_all_fields() -> list[int]: + """ + Returns the ids of all Yu-Gi-Oh! Field Spell cards in + `db.ygoprodeck.com` database + """ + + try: + response = make_request( + YGOPRODECK_CARDS_URL, + params={"type": "spell card","race": "field"} + ) + return _get_ids_from_response(response) + except Exception as e: + print(f"Error fetching db.ygoprodeck.com: {type(e).__name__}\n{e}") + + return list()