├── .gitignore
├── requirements.txt
├── id_conversor.py
├── web_access
├── request_helper.py
├── downloader.py
└── ygoprodeck_api.py
├── commands
├── cmd_exit.py
├── cmd_allcards.py
├── cmd_allfields.py
├── cmd_alltokens.py
├── cmd_all.py
├── utils.py
├── cmd_force.py
├── cmd_help.py
├── setup.py
└── typing.py
├── input_handler.py
├── Makefile
├── README.md
├── tracker.py
├── deckread.py
├── LICENSE
├── command_handler.py
├── constants.py
└── main.py
/.gitignore:
--------------------------------------------------------------------------------
1 | **/__pycache__/
2 | .vscode/
3 | deck/
4 | pics/
5 | build/
6 | dist/
7 | bin/
8 | workpath_pyinstaller/
9 | edopro-hd-downloader-venv/
10 |
11 | hd_cards_downloader_tracker
12 | hd_fields_downloader_tracker
13 | HdDownloader.LICENSE.txt
14 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | altgraph==0.17.4
2 | certifi==2024.2.2
3 | charset-normalizer==3.3.2
4 | idna==3.6
5 | packaging==23.2
6 | pefile==2023.2.7
7 | pyinstaller==6.3.0
8 | pyinstaller-hooks-contrib==2024.0
9 | pywin32-ctypes==0.2.2
10 | requests==2.31.0
11 | setuptools==69.0.3
12 | urllib3==2.2.0
13 |
--------------------------------------------------------------------------------
/id_conversor.py:
--------------------------------------------------------------------------------
1 | from constants import ID_CONVERSION
2 |
3 | def convert_id(card_id: int) -> int:
4 | """
5 | Converts cards id from YGOProDeck database to EDOPro database
6 |
7 | See https://github.com/NiiMiyo/EDOPro-Hd-Downloader/issues/8
8 | """
9 |
10 | return ID_CONVERSION.get(card_id, card_id)
11 |
--------------------------------------------------------------------------------
/web_access/request_helper.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Optional
2 | import requests
3 | from constants import REQUEST_HEADERS
4 |
5 | def make_request(
6 | url: str,
7 | params: Optional[dict[Any, Any]] = None
8 | ) -> requests.Response:
9 |
10 | return requests.get(url, headers=REQUEST_HEADERS, params=params)
11 |
--------------------------------------------------------------------------------
/commands/cmd_exit.py:
--------------------------------------------------------------------------------
1 | from commands.typing import CommandReturn, DownloaderCommand
2 |
3 |
4 | def __cmd_exit_action(_: str) -> CommandReturn:
5 | print("Bye bye <3")
6 | exit(0)
7 |
8 |
9 | COMMAND_EXIT = DownloaderCommand(
10 | name="exit",
11 | help_text="closes the program",
12 | action=__cmd_exit_action
13 | )
14 |
--------------------------------------------------------------------------------
/commands/cmd_allcards.py:
--------------------------------------------------------------------------------
1 | from commands.typing import CommandReturn, DownloadCard, DownloaderCommand
2 | from web_access.ygoprodeck_api import get_all_cards
3 |
4 |
5 | def __cmd_all_cards_action(_: str) -> CommandReturn:
6 | return [
7 | DownloadCard(c, False)
8 | for c in get_all_cards()
9 | ]
10 |
11 |
12 | COMMAND_ALLCARDS = DownloaderCommand(
13 | name="allcards",
14 | help_text="downloads all cards",
15 | action=__cmd_all_cards_action
16 | )
17 |
--------------------------------------------------------------------------------
/commands/cmd_allfields.py:
--------------------------------------------------------------------------------
1 | from commands.typing import DownloadCard, DownloaderCommand, CommandReturn
2 | from web_access.ygoprodeck_api import get_all_fields
3 |
4 | def __cmd_all_fields_action(_: str) -> CommandReturn:
5 | return [
6 | DownloadCard(c, True)
7 | for c in get_all_fields()
8 | ]
9 |
10 |
11 | COMMAND_ALLFIELDS = DownloaderCommand(
12 | name="allfields",
13 | help_text="downloads all fields artworks",
14 | action=__cmd_all_fields_action
15 | )
--------------------------------------------------------------------------------
/commands/cmd_alltokens.py:
--------------------------------------------------------------------------------
1 | from commands.typing import DownloadCard, DownloaderCommand, CommandReturn
2 | from web_access.ygoprodeck_api import get_all_tokens
3 |
4 | def __cmd_all_tokens_action(_: str) -> CommandReturn:
5 | return [
6 | DownloadCard(c, False, False)
7 | for c in get_all_tokens()
8 | ]
9 |
10 |
11 | COMMAND_ALLTOKENS = DownloaderCommand(
12 | name="alltokens",
13 | help_text="downloads all tokens",
14 | action=__cmd_all_tokens_action
15 | )
16 |
--------------------------------------------------------------------------------
/commands/cmd_all.py:
--------------------------------------------------------------------------------
1 | from commands.typing import CommandReturn, DownloaderCommand
2 | from command_handler import CommandHandler
3 |
4 |
5 | def __cmd_all(_: str) -> CommandReturn:
6 | allcards = CommandHandler.commands.get("allcards")
7 | allfields = CommandHandler.commands.get("allfields")
8 |
9 | return allcards.action(_) + allfields.action(_) # type: ignore
10 |
11 |
12 | COMMAND_ALL = DownloaderCommand(
13 | name="all",
14 | help_text="downloads all cards images and all fields artworks",
15 | action=__cmd_all
16 | )
17 |
--------------------------------------------------------------------------------
/input_handler.py:
--------------------------------------------------------------------------------
1 | from command_handler import CommandHandler
2 | from commands.typing import CommandReturn
3 | from deckread import get_deck
4 |
5 | def handle_input(user_input: str) -> CommandReturn:
6 | """Handles an user input and returns a `CommandReturn` according to the
7 | matching command or deck with same name.
8 |
9 | Returns `None` if couldn't find what do download
10 | """
11 |
12 | command = CommandHandler.find_command(user_input)
13 | if command is None:
14 | return get_deck(user_input)
15 | else:
16 | return command.action(user_input)
17 |
--------------------------------------------------------------------------------
/commands/utils.py:
--------------------------------------------------------------------------------
1 | def get_args(user_input: str) -> str:
2 | """Receives an user input and returns everything after the first
3 | space character replacing double spaces with single spaces
4 | """
5 |
6 | args = [
7 | a for a in user_input.split(" ")
8 | if a
9 | ]
10 |
11 | return " ".join(args[1:])
12 |
13 |
14 | def get_first_word(user_input: str) -> str:
15 | """Receives an user input and returns everything before the first
16 | space character
17 | """
18 |
19 | if not user_input:
20 | return ""
21 |
22 | return user_input.split(" ")[0]
23 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | FILENAME = EDOPro-HD-Downloader
2 | LICENSE = LICENSE
3 | LICENSE_BIN = HdDownloader.LICENSE.txt
4 | DISTPATH = bin
5 | WORKPATH = workpath_pyinstaller
6 | ZIP_WIN = windows.zip
7 |
8 | build:
9 | pyinstaller main.py -y --distpath "$(DISTPATH)" -F --specpath "$(DISTPATH)" -n "$(FILENAME)" -c --clean --workpath "$(WORKPATH)"
10 | cp "$(LICENSE)" "$(DISTPATH)/$(LICENSE_BIN)"
11 |
12 | rm -rf "$(WORKPATH)"
13 |
14 | 7z-win: build
15 | cd "$(DISTPATH)" && 7z a "$(ZIP_WIN)" "$(FILENAME).exe" "$(LICENSE_BIN)"
16 |
17 | clean:
18 | rm -rf "hd_cards_downloader_tracker"
19 | rm -rf "hd_fields_downloader_tracker"
20 |
--------------------------------------------------------------------------------
/commands/cmd_force.py:
--------------------------------------------------------------------------------
1 | from commands.typing import CommandReturn, DownloadCard, DownloaderCommand
2 | from commands.utils import get_args
3 | from input_handler import handle_input
4 |
5 | def __cmd_force_action(user_input: str) -> CommandReturn:
6 | args = get_args(user_input)
7 | cards = handle_input(args)
8 | if cards is None:
9 | return None
10 |
11 | return [
12 | DownloadCard(c.card_id, c.artwork, True)
13 | for c in cards
14 | ]
15 |
16 | COMMAND_FORCE = DownloaderCommand(
17 | name="force",
18 | shown_name="force ",
19 | help_text="executes ignoring trackers (example: /force /allcards)",
20 | action=__cmd_force_action
21 | )
22 |
--------------------------------------------------------------------------------
/commands/cmd_help.py:
--------------------------------------------------------------------------------
1 | from commands.typing import CommandReturn, DownloaderCommand
2 | from command_handler import CommandHandler
3 |
4 |
5 | def __cmd_help_action(_: str) -> CommandReturn:
6 | cmd_column_len = 0
7 | lines: list[tuple[str, str]] = list()
8 |
9 | command_list = sorted(
10 | CommandHandler.commands.values(),
11 | key=lambda cmd: cmd.name
12 | )
13 |
14 | for cmd in command_list:
15 | sn = cmd.get_shown_name()
16 |
17 | lines.append((sn, cmd.help_text))
18 | cmd_column_len = max(cmd_column_len, len(sn))
19 |
20 | print("\n".join(
21 | f"/{sn.ljust(cmd_column_len)} - {ht}"
22 | for sn, ht in lines
23 | ))
24 |
25 | return []
26 |
27 |
28 | COMMAND_HELP = DownloaderCommand(
29 | name="help",
30 | help_text="see this text",
31 | action=__cmd_help_action
32 | )
33 |
--------------------------------------------------------------------------------
/web_access/downloader.py:
--------------------------------------------------------------------------------
1 | import requests
2 | from os.path import join
3 |
4 | from commands.typing import DownloadCard
5 | from constants import IMAGES_BASE_URL
6 | from id_conversor import convert_id
7 |
8 | def download_image(card: DownloadCard) -> bool:
9 | """
10 | Downloads the card image or artwork and puts in the specified folder.
11 |
12 | Returns `True` if downloads successfully, otherwise returns `False`.
13 | """
14 |
15 | url = IMAGES_BASE_URL
16 | store_at = "./pics/"
17 |
18 | if card.artwork:
19 | url += "_cropped"
20 | store_at += "field/"
21 | url += f"/{card.card_id}.jpg"
22 |
23 | file_path = join(store_at, f"{convert_id(card.card_id)}.jpg")
24 | try:
25 | res = requests.get(url)
26 | with open(file_path, 'wb+') as f:
27 | f.write(res.content)
28 | return True
29 | except Exception as e:
30 | print(f"Error downloading '{card.card_id}': {type(e).__name__}\n{e}")
31 | return False
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # EDOPro HD Downloader
2 |
3 | # Installation
4 |
5 | ## Windows
6 |
7 | Download the [latest Windows release](https://github.com/NiiMiyo/EDOPro-Hd-Downloader/releases/latest) and unzip it at your EDOPro folder (Default should be something like `C:/ProjectIgnis/`).
8 |
9 | ## Non-Windows
10 |
11 | Since I can't test non-windows compilations you will need to download the source code and compile it yourself. Then put the compiled file at your EDOPro folder.
12 |
13 |
14 | # Usage
15 |
16 | If you run the program and read the instructions you should be fine.
17 |
18 | But for short:
19 |
20 | - Insert the name of your deck (without the `.ydk` extension) when asked to download all the images of the cards in it.
21 |
22 | - Insert `/allcards` to download images for all cards. Will probably take a while.
23 |
24 | - Insert `/allfields` to download artwork of all Field Spell Cards.
25 |
26 | - Insert `/help` if you want to know anything else.
27 |
28 | # License
29 |
30 | [MIT](https://douglas-sebastian.mit-license.org)
31 |
--------------------------------------------------------------------------------
/tracker.py:
--------------------------------------------------------------------------------
1 | from commands.typing import DownloadCard
2 | from constants import CARD_CACHE_PATH, FIELD_CACHE_PATH
3 |
4 |
5 | def __get_cached(is_artwork: bool):
6 | """Reads tracker file and returns a list of ids
7 | that already were downloaded"""
8 |
9 | cache = FIELD_CACHE_PATH if is_artwork else CARD_CACHE_PATH
10 | with open(cache, mode="r+", encoding="utf8") as cache_file:
11 | cards = [c.strip() for c in cache_file.readlines()]
12 | return cards
13 |
14 | def already_downloaded(card: DownloadCard):
15 | """Returns True if card with id 'card.card_id' was
16 | already downloaded"""
17 |
18 | cards_downloaded = __get_cached(card.artwork)
19 | return str(card.card_id) in cards_downloaded
20 |
21 | def mark_as_downloaded(card: DownloadCard):
22 | """Opens tracker file to add an id to the downloaded list"""
23 |
24 | cache = FIELD_CACHE_PATH if card.artwork else CARD_CACHE_PATH
25 |
26 | with open(cache, mode="a+", encoding="utf8") as cache_file:
27 | cache_file.write(f"{card.card_id}\n")
28 |
--------------------------------------------------------------------------------
/deckread.py:
--------------------------------------------------------------------------------
1 | from os.path import exists as __exists, join as __join
2 |
3 | from commands.typing import CommandReturn, DownloadCard
4 |
5 | deck_folder_path = "./deck/"
6 |
7 |
8 | def __filter_card_id(cards: list[str]) -> list[int]:
9 | """Filters an list with card ids to remove
10 | repeating ones and non-ids"""
11 |
12 | ids: list[int] = list()
13 | for c in cards:
14 | try:
15 | c = int(c)
16 | if c not in ids:
17 | ids.append(int(c))
18 | except ValueError:
19 | continue
20 | return ids
21 |
22 |
23 | def get_deck(deck_name: str) -> CommandReturn:
24 | """Reads a deck file and returns the
25 | ids of the cards in it"""
26 |
27 | deck_path = __join(deck_folder_path, f"{deck_name}.ydk")
28 | deck_exists = __exists(deck_path)
29 | if not deck_exists:
30 | return None
31 | deck = open(deck_path, mode="r", encoding="utf8")
32 | cards = __filter_card_id([l.strip() for l in deck.readlines()])
33 | return [
34 | DownloadCard(c, False)
35 | for c in cards
36 | ]
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright © 2024 Douglas Sebastian
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the “Software”), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/commands/setup.py:
--------------------------------------------------------------------------------
1 | from command_handler import CommandHandler
2 | from commands.cmd_allcards import COMMAND_ALLCARDS
3 | from commands.cmd_all import COMMAND_ALL
4 | from commands.cmd_allfields import COMMAND_ALLFIELDS
5 | from commands.cmd_alltokens import COMMAND_ALLTOKENS
6 | from commands.cmd_exit import COMMAND_EXIT
7 | from commands.cmd_force import COMMAND_FORCE
8 | from commands.cmd_help import COMMAND_HELP
9 | from constants import SETUP_CREATION_FILES, SETUP_CREATION_FOLDERS
10 |
11 | from os.path import exists
12 | from os import makedirs
13 |
14 | def setup_commands():
15 | CommandHandler.add_command(COMMAND_ALL)
16 | CommandHandler.add_command(COMMAND_ALLCARDS)
17 | CommandHandler.add_command(COMMAND_ALLFIELDS)
18 | CommandHandler.add_command(COMMAND_EXIT)
19 | CommandHandler.add_command(COMMAND_FORCE)
20 | CommandHandler.add_command(COMMAND_HELP)
21 | CommandHandler.add_command(COMMAND_ALLTOKENS)
22 |
23 | def setup_files():
24 | for f in SETUP_CREATION_FILES:
25 | if not exists(f):
26 | with open(f, "w+"):
27 | # I only need that the files exist
28 | pass
29 |
30 | def setup_folders():
31 | for f in SETUP_CREATION_FOLDERS:
32 | if not exists(f):
33 | makedirs(f)
34 |
--------------------------------------------------------------------------------
/command_handler.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 | from commands.typing import DownloaderCommand
3 | from commands.utils import get_first_word
4 |
5 |
6 | class CommandHandler:
7 | """Class to manage the use of commands"""
8 |
9 | commands: dict[str, DownloaderCommand] = dict()
10 | """Dict of all available commands (maps command name to command).
11 |
12 | If you add a command you should put it here using
13 | `CommandHandler.add_command` and adding the import on `commands/setup.py`.
14 | """
15 |
16 | @staticmethod
17 | def find_command(user_input: str) -> Optional[DownloaderCommand]:
18 | """Gets the `DownloaderCommand` that matches user_input. For example,
19 | `force` command for `"/force /allcards"` input.
20 | """
21 |
22 | if not user_input: # If empty string or None
23 | return None
24 |
25 | command_used = get_first_word(user_input)
26 | if command_used.startswith("/"):
27 | return CommandHandler.commands.get(command_used[1:])
28 | else:
29 | return None
30 |
31 | @staticmethod
32 | def add_command(command: DownloaderCommand):
33 | """Adds a DownloaderCommand to be available to use on user input"""
34 |
35 | CommandHandler.commands[command.name] = command
36 |
--------------------------------------------------------------------------------
/constants.py:
--------------------------------------------------------------------------------
1 | DOWNLOADER_VERSION = "2.3"
2 | """Program version"""
3 |
4 | REQUEST_HEADERS = {
5 | "User-Agent": f"NiiMiyo-EDOPro-HD-Downloader/{DOWNLOADER_VERSION}"
6 | }
7 | """Header to be used in an HTTP request"""
8 |
9 | INTRO_STRING = f"""EDOPro HD Downloader v{DOWNLOADER_VERSION}
10 | Created by Nii Miyo
11 | Type "/help" for help"""
12 | """String to be used when starting the program"""
13 |
14 | INPUT_STRING = "Insert deck name (without .ydk) or command: "
15 | """String that appears at user input"""
16 |
17 | YGOPRODECK_CARDS_URL = "https://db.ygoprodeck.com/api/v7/cardinfo.php"
18 | """Base API URL for YGOProDeck"""
19 |
20 | IMAGES_BASE_URL = "https://images.ygoprodeck.com/images/cards"
21 | """Base URL for images"""
22 |
23 | CARD_CACHE_PATH = "./hd_cards_downloader_tracker"
24 | """Path to the cards cache file"""
25 |
26 | FIELD_CACHE_PATH = "./hd_fields_downloader_tracker"
27 | """Path to the fields cache file"""
28 |
29 | SETUP_CREATION_FILES = (CARD_CACHE_PATH, FIELD_CACHE_PATH)
30 | """Files needed on setup"""
31 |
32 | SETUP_CREATION_FOLDERS = ("pics/field",)
33 | """Folders needed on setup"""
34 |
35 | SLEEP_TIME_BETWEEN_DOWNLOADS = 1 / 18
36 | """Time in seconds that the program will wait before downloading the next card"""
37 |
38 | ID_CONVERSION: dict[int, int] = {
39 | # Mecha Phantom Beast Token
40 | 904186: 31533705
41 | }
42 |
--------------------------------------------------------------------------------
/commands/typing.py:
--------------------------------------------------------------------------------
1 | from typing import Callable, NamedTuple, Optional
2 |
3 |
4 | class DownloadCard(NamedTuple):
5 | """Represents a card to be downloaded"""
6 |
7 | card_id: int
8 | artwork: bool
9 | force: bool = False
10 |
11 |
12 | CommandReturn = Optional[list[DownloadCard]]
13 | """Type a `CommandAction` should return"""
14 |
15 | CommandAction = Callable[[str], CommandReturn]
16 | """Type a `DownloaderCommand.action` should be.
17 |
18 | Should only return `None` in case the command fails. If the command does not
19 | download cards, return an empty list.
20 | """
21 |
22 |
23 | class DownloaderCommand(NamedTuple):
24 | name: str
25 | """The name of the command, should be unique"""
26 |
27 | help_text: str
28 | """Text the command shows on `/help`"""
29 |
30 | action: CommandAction
31 | """Function that defines command execution"""
32 |
33 | shown_name: Optional[str] = None
34 | """Name that will be shown on `/help`. If it's `None` then shows `name`"""
35 |
36 | def match_string(self) -> str:
37 | """Returns `/command.name`"""
38 | return f"/{self.name}"
39 |
40 | def get_shown_name(self) -> str:
41 | """Returns `command.shown_name` if it's not None. Otherwise returns
42 | `command.name`
43 | """
44 |
45 | if self.shown_name is None:
46 | return self.name
47 | else:
48 | return self.shown_name
49 |
--------------------------------------------------------------------------------
/web_access/ygoprodeck_api.py:
--------------------------------------------------------------------------------
1 | from requests import Response
2 | from constants import YGOPRODECK_CARDS_URL
3 | from web_access.request_helper import make_request
4 |
5 |
6 | def _get_ids_from_response(response: Response) -> list[int]:
7 | """Returns only the ids of the cards requested"""
8 | data = response.json().get("data")
9 | ids: list[int] = list()
10 |
11 | for c in data:
12 | for img in c.get("card_images"):
13 | ids.append(img.get("id"))
14 | return ids
15 |
16 | def get_all_cards() -> list[int]:
17 | """Returns the ids of all Yu-Gi-Oh! cards in `db.ygoprodeck.com` database"""
18 |
19 | try:
20 | response = make_request(YGOPRODECK_CARDS_URL)
21 | return _get_ids_from_response(response)
22 | except Exception as e:
23 | print(f"Error fetching db.ygoprodeck.com: {type(e).__name__}\n{e}")
24 |
25 | return list()
26 |
27 | def get_all_fields() -> list[int]:
28 | """
29 | Returns the ids of all Yu-Gi-Oh! Field Spell cards in
30 | `db.ygoprodeck.com` database
31 | """
32 |
33 | try:
34 | response = make_request(
35 | YGOPRODECK_CARDS_URL,
36 | params={"type": "spell card","race": "field"}
37 | )
38 | return _get_ids_from_response(response)
39 | except Exception as e:
40 | print(f"Error fetching db.ygoprodeck.com: {type(e).__name__}\n{e}")
41 |
42 | return list()
43 |
44 | def get_all_tokens() -> list[int]:
45 | """
46 | Return the ids of all Tokens in the `db.ygoprodeck.com` database
47 | """
48 |
49 | try:
50 | response = make_request(
51 | YGOPRODECK_CARDS_URL,
52 | params={"type": "token"}
53 | )
54 | return _get_ids_from_response(response)
55 | except Exception as e:
56 | print(f"Error fetching db.ygoprodeck.com: {type(e).__name__}\n{e}")
57 |
58 | return list()
59 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | from time import sleep
2 | from traceback import print_exc
3 | from commands.setup import setup_commands, setup_files, setup_folders
4 | from constants import INPUT_STRING, INTRO_STRING, SLEEP_TIME_BETWEEN_DOWNLOADS
5 |
6 | from input_handler import handle_input
7 | from commands.typing import DownloadCard
8 | from web_access.downloader import download_image
9 | from tracker import (already_downloaded, mark_as_downloaded)
10 |
11 |
12 | def initialize():
13 | """Creates tracker files if they do not exist, setups all commands and
14 | introduces the program
15 | """
16 |
17 | setup_files()
18 | setup_folders()
19 | setup_commands()
20 | print(INTRO_STRING)
21 |
22 |
23 | def to_download(card: DownloadCard):
24 | """Handles if a card should be downloaded and downloads it."""
25 |
26 | if (card.force) or (not already_downloaded(card)):
27 | success = download_image(card)
28 | if success: mark_as_downloaded(card)
29 | sleep(SLEEP_TIME_BETWEEN_DOWNLOADS)
30 |
31 |
32 | def main():
33 | initialize()
34 |
35 | try:
36 | while True:
37 | cards = handle_input( input(INPUT_STRING) )
38 |
39 | # If couldn't find what to download
40 | if cards is None:
41 | print("Deck or command not found.")
42 | continue
43 |
44 | total_cards = len(cards)
45 |
46 | # For each card, download
47 | for index, card in enumerate(cards, 1):
48 | to_download(card)
49 |
50 | # Prints progress
51 | raw_progress = f"{index}/{total_cards}"
52 | percentage = f"{((index * 100) / total_cards):.2f}%"
53 | print(f"Downloaded {raw_progress} - {percentage}", end="\r")
54 |
55 | print("\n")
56 |
57 | # In case of interrupting the program with Ctrl+C
58 | except KeyboardInterrupt: print("\n\nForcing program interruption...")
59 |
60 | # In case of a not expected exception
61 | except Exception: print_exc()
62 |
63 |
64 | if __name__ == "__main__": main()
65 |
--------------------------------------------------------------------------------