mtg-decks-downloader

Tool to download Magic: The Gathering decklists from the Internet
git clone https://kevincorvisier.fr/git/mtg-decks-downloader.git
Log | Files | Refs | README

commit 82650f22099a0afd272532207ad1f48c31f7b15e
parent 65174f210d9ba5c3f74e6975fec36fc973bb5a95
Author: Kevin Corvisier <git@kevincorvisier.fr>
Date:   Thu, 31 Oct 2024 10:15:16 +0900

Add possibility to save a card pool file containing all cards of decks
downloaded, even if they were not included in the final list of decks
Diffstat:
Msrc/main/java/fr/kevincorvisier/mtg/dd/Main.java | 6+++---
Asrc/main/java/fr/kevincorvisier/mtg/dd/consumers/CardPoolConsumer.java | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main/java/fr/kevincorvisier/mtg/dd/consumers/DecklistConsumersService.java | 15+++++++++++++++
Msrc/main/java/fr/kevincorvisier/mtg/dd/consumers/DefaultDecklistConsumer.java | 4+---
Msrc/main/java/fr/kevincorvisier/mtg/dd/downloaders/HareruyaDecklistDownloader.java | 5+++++
Msrc/main/java/fr/kevincorvisier/mtg/dd/validation/DeckValidator.java | 25+++++++++++++++++--------
Msrc/main/packaged-resources/cfg/application.properties | 5+++++
7 files changed, 120 insertions(+), 14 deletions(-)

diff --git a/src/main/java/fr/kevincorvisier/mtg/dd/Main.java b/src/main/java/fr/kevincorvisier/mtg/dd/Main.java @@ -12,7 +12,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.core.io.support.ResourcePropertySource; import org.springframework.stereotype.Service; -import fr.kevincorvisier.mtg.dd.consumers.DecklistConsumer; +import fr.kevincorvisier.mtg.dd.consumers.DecklistConsumersService; import fr.kevincorvisier.mtg.dd.downloaders.DecklistDownloader; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -22,7 +22,7 @@ import lombok.extern.slf4j.Slf4j; @RequiredArgsConstructor public class Main { - private final DecklistConsumer consumer; + private final DecklistConsumersService consumers; private final Collection<DecklistDownloader> downloaders; @Value("#{'${sources}'.split('\\|')}") @@ -46,7 +46,7 @@ public class Main } finally { - consumer.saveToFolder(); + consumers.saveToFolder(); } } diff --git a/src/main/java/fr/kevincorvisier/mtg/dd/consumers/CardPoolConsumer.java b/src/main/java/fr/kevincorvisier/mtg/dd/consumers/CardPoolConsumer.java @@ -0,0 +1,74 @@ +package fr.kevincorvisier.mtg.dd.consumers; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Collection; +import java.util.HashSet; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import fr.kevincorvisier.mtg.dd.model.Deck; +import fr.kevincorvisier.mtg.dd.model.DeckMetadata; +import fr.kevincorvisier.mtg.dd.validation.DeckValidator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +public class CardPoolConsumer implements DecklistConsumer +{ + private final Collection<String> cardPool = new HashSet<>(); + private final DeckValidator validator; + + @Value("${card-pool.enabled}") + private final boolean enabled; + @Value("${card-pool.output-file}") + private final File outputFile; + + @Override + public boolean accept(final DeckMetadata metadata) + { + return enabled; + } + + @Override + public void consume(final Deck deck) + { + for (final String cardName : deck.getContent().getMain().keySet()) + { + if (validator.validateCard(cardName)) + cardPool.add(cardName); + } + } + + @Override + public void saveToFolder() throws IOException + { + if (!enabled) + return; + + final File folder = outputFile.getParentFile(); + if (!folder.exists()) + folder.mkdirs(); + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) + { + for (final String cardName : cardPool) + { + writer.append(sanitizeCardName(cardName)).append('\n'); + } + } + + log.info("Saved {}", outputFile); + } + + private static String sanitizeCardName(final String cardName) + { + final int indexOfSlash = cardName.indexOf("/"); + return indexOfSlash != -1 ? cardName.substring(0, indexOfSlash) : cardName; + } +} diff --git a/src/main/java/fr/kevincorvisier/mtg/dd/consumers/DecklistConsumersService.java b/src/main/java/fr/kevincorvisier/mtg/dd/consumers/DecklistConsumersService.java @@ -54,4 +54,19 @@ public class DecklistConsumersService consumer.consume(new Deck(metadata, content)); } } + + public void saveToFolder() + { + for (final DecklistConsumer consumer : consumers) + { + try + { + consumer.saveToFolder(); + } + catch (final IOException e) + { + log.error("saveToFolder: consumer={}", consumer, e); + } + } + } } diff --git a/src/main/java/fr/kevincorvisier/mtg/dd/consumers/DefaultDecklistConsumer.java b/src/main/java/fr/kevincorvisier/mtg/dd/consumers/DefaultDecklistConsumer.java @@ -40,8 +40,6 @@ public class DefaultDecklistConsumer implements DecklistConsumer, StopCondition private final DeckValidator validator; - @Value("${only-ai-playable-cards}") - private final boolean onlyAiPlayableCards; @Value("${output-dir}") private final File folder; @Value("${ignore.players}") @@ -98,7 +96,7 @@ public class DefaultDecklistConsumer implements DecklistConsumer, StopCondition return; } - if (!validator.validate(deck, onlyAiPlayableCards)) + if (!validator.validate(deck)) return; downloadedByDeckNameByPlayerName.computeIfAbsent(deck.getMetadata().getPlayer().toLowerCase(), k -> new HashMap<>()) // diff --git a/src/main/java/fr/kevincorvisier/mtg/dd/downloaders/HareruyaDecklistDownloader.java b/src/main/java/fr/kevincorvisier/mtg/dd/downloaders/HareruyaDecklistDownloader.java @@ -9,6 +9,7 @@ import org.openqa.selenium.WebElement; import org.springframework.stereotype.Service; import fr.kevincorvisier.mtg.dd.Crawler; +import fr.kevincorvisier.mtg.dd.StopCondition; import fr.kevincorvisier.mtg.dd.consumers.DecklistConsumersService; import fr.kevincorvisier.mtg.dd.model.DeckMetadata; import fr.kevincorvisier.mtg.dd.model.DeckMetadataFactory; @@ -23,6 +24,7 @@ public class HareruyaDecklistDownloader implements DecklistDownloader private final Crawler crawler; private final DeckMetadataFactory metadataFactory; private final DecklistConsumersService consumers; + private final StopCondition stopCondition; @Override public boolean accept(final URL url) @@ -41,6 +43,9 @@ public class HareruyaDecklistDownloader implements DecklistDownloader { final DeckMetadata deck = processDeckElement(element); consumers.process(deck); + + if (stopCondition.capacity() <= 0) + return; } catch (final IOException e) { diff --git a/src/main/java/fr/kevincorvisier/mtg/dd/validation/DeckValidator.java b/src/main/java/fr/kevincorvisier/mtg/dd/validation/DeckValidator.java @@ -22,6 +22,9 @@ public class DeckValidator private final Collection<String> banlist = new HashSet<>(); private final AiCards aiCards; + @Value("${only-ai-playable-cards}") + private final boolean onlyAiPlayableCards; + @Value("${banlists}") public void setBanlistFileNames(final Collection<String> banlistFileNames) throws IOException { @@ -35,22 +38,17 @@ public class DeckValidator } } - public boolean validate(final Deck deck, final boolean onlyAiPlayableCards) + public boolean validate(final Deck deck) { int cardCount = 0; for (final Entry<String, Integer> card : deck.getContent().getMain().entrySet()) { final String cardName = card.getKey(); - if (banlist.contains(cardName)) - { - log.debug("validate: invalid deck: {} is banned: {}", cardName, deck); - return false; - } - if (onlyAiPlayableCards && !aiCards.isPlayableByAi(cardName)) + if (!validateCard(cardName)) { - log.debug("validate: {} is not playable by AI: {}", cardName, deck); + log.debug("validate: {} is not valid: {}", cardName, deck); return false; } @@ -66,6 +64,17 @@ public class DeckValidator return true; } + public boolean validateCard(final String name) + { + if (banlist.contains(name)) + return false; + + if (onlyAiPlayableCards && !aiCards.isPlayableByAi(name)) + return false; + + return true; + } + private void readBanlist(final String name) throws IOException { try (BufferedReader reader = new BufferedReader(new InputStreamReader(getClass().getClassLoader().getResourceAsStream(name)))) diff --git a/src/main/packaged-resources/cfg/application.properties b/src/main/packaged-resources/cfg/application.properties @@ -22,3 +22,8 @@ only-ai-playable-cards=true # Minimum number of players for a tournament from TCDecks to be taken into account, tournament with less players will be ignored tcdecks.tournament.players.min=0 +# When enabled, a card pool is created containing cards from all decks that have been considered for inclusion +card-pool.enabled=false +# Name of the file to save the card pool to +card-pool.output-file= +