From 1d8e7a0cb31874d538f8640389bc2bda71d710b5 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 26 Jul 2019 18:39:38 +0200 Subject: [PATCH] Add Download HQ Card Pictures from Scryfall --- .../home/settings/CSubmenuDownloaders.java | 6 + .../home/settings/VSubmenuDownloaders.java | 5 + forge-gui/res/languages/de-DE.properties | 2 + forge-gui/res/languages/en-US.properties | 2 + forge-gui/res/languages/es-es.properties | 2 + .../forge/download/GuiDownloadPicturesHQ.java | 356 ++++++++++++++++++ .../forge/download/GuiDownloadService.java | 9 +- .../java/forge/properties/ForgeConstants.java | 2 + 8 files changed, 383 insertions(+), 1 deletion(-) create mode 100644 forge-gui/src/main/java/forge/download/GuiDownloadPicturesHQ.java diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuDownloaders.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuDownloaders.java index 79a7f188bb8..53a0814f8c3 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuDownloaders.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuDownloaders.java @@ -28,6 +28,11 @@ public enum CSubmenuDownloaders implements ICDoc { new GuiDownloader(new GuiDownloadPicturesLQ()).show(); } }; + private final UiCommand cmdPicDownloadHQ = new UiCommand() { + @Override public void run() { + new GuiDownloader(new GuiDownloadPicturesHQ()).show(); + } + }; private final UiCommand cmdSetDownload = new UiCommand() { @Override public void run() { new GuiDownloader(new GuiDownloadSetPicturesLQ()).show(); @@ -80,6 +85,7 @@ public enum CSubmenuDownloaders implements ICDoc { public void initialize() { final VSubmenuDownloaders view = VSubmenuDownloaders.SINGLETON_INSTANCE; view.setDownloadPicsCommand(cmdPicDownload); + view.setDownloadPicsHQCommand(cmdPicDownloadHQ); view.setDownloadSetPicsCommand(cmdSetDownload); view.setDownloadQuestImagesCommand(cmdQuestImages); view.setDownloadAchievementImagesCommand(cmdAchievementImages); diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuDownloaders.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuDownloaders.java index 02cf1bfbb62..44c3e2230b9 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuDownloaders.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuDownloaders.java @@ -57,6 +57,7 @@ public enum VSubmenuDownloaders implements IVSubmenu { private final FLabel btnDownloadSetPics = _makeButton(localizer.getMessage("btnDownloadSetPics")); private final FLabel btnDownloadPics = _makeButton(localizer.getMessage("btnDownloadPics")); + private final FLabel btnDownloadPicsHQ = _makeButton(localizer.getMessage("btnDownloadPicsHQ")); private final FLabel btnDownloadQuestImages = _makeButton(localizer.getMessage("btnDownloadQuestImages")); private final FLabel btnDownloadAchievementImages = _makeButton(localizer.getMessage("btnDownloadAchievementImages")); private final FLabel btnReportBug = _makeButton(localizer.getMessage("btnReportBug")); @@ -82,6 +83,9 @@ public enum VSubmenuDownloaders implements IVSubmenu { pnlContent.add(btnDownloadPics, constraintsBTN); pnlContent.add(_makeLabel(localizer.getMessage("lblDownloadPics")), constraintsLBL); + pnlContent.add(btnDownloadPicsHQ, constraintsBTN); + pnlContent.add(_makeLabel(localizer.getMessage("lblDownloadPicsHQ")), constraintsLBL); + pnlContent.add(btnDownloadSetPics, constraintsBTN); pnlContent.add(_makeLabel(localizer.getMessage("lblDownloadSetPics")), constraintsLBL); @@ -159,6 +163,7 @@ public enum VSubmenuDownloaders implements IVSubmenu { } public void setDownloadPicsCommand(UiCommand command) { btnDownloadPics.setCommand(command); } + public void setDownloadPicsHQCommand(UiCommand command) { btnDownloadPicsHQ.setCommand(command); } public void setDownloadSetPicsCommand(UiCommand command) { btnDownloadSetPics.setCommand(command); } public void setDownloadQuestImagesCommand(UiCommand command) { btnDownloadQuestImages.setCommand(command); } public void setDownloadAchievementImagesCommand(UiCommand command) { btnDownloadAchievementImages.setCommand(command); } diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 5cfecfde4be..66edc2f8d10 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -179,6 +179,7 @@ Achievements=Erfolge # VSubmenuDownloaders.java btnDownloadSetPics=Bilder(LQ) Sets herunterladen btnDownloadPics=Bilder(LQ) Karten herunterladen +btnDownloadPicsHQ=Bilder(HQ) Karten herunterladen (Sehr langsam!) btnDownloadQuestImages=Bilder für Quests herunterladen btnDownloadAchievementImages=Bilder für Erfolge herunterladen btnReportBug=Einen Fehler melden @@ -189,6 +190,7 @@ btnHowToPlay=Wie man spielt btnDownloadPrices=Kartenpreise herunterladen btnLicensing=Lizenzhinweis lblDownloadPics=Lädt ein Standardbild pro Karte. +lblDownloadPicsHQ=Lädt ein Standardbild (HQ) pro Karte. lblDownloadSetPics=Lädt alle Bilder pro Karte. Eines für jedes Set, in welchem die Karte auftauchte. lblDownloadQuestImages=Lädt die Bilder für den Quest-Modus. lblDownloadAchievementImages=Lädt die Bilder zu den möglichen Erfolgen. Verschönert die Trophäensammlung. diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index cedefb81c72..09e748829d7 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -178,6 +178,7 @@ KeyboardShortcuts=Keyboard Shortcuts Achievements=Achievements # VSubmenuDownloaders.java btnDownloadSetPics=Download LQ Set Pictures +btnDownloadPicsHQ=Download HQ Card Pictures (Very Slow!) btnDownloadPics=Download LQ Card Pictures btnDownloadQuestImages=Download Quest Images btnDownloadAchievementImages=Download Achievement Images @@ -189,6 +190,7 @@ btnHowToPlay=How To Play btnDownloadPrices=Download Card Prices btnLicensing=License Details lblDownloadPics=Download default card picture for each card. +lblDownloadPicsHQ=Download default card HQ picture for each card. lblDownloadSetPics=Download all pictures of each card (one for each set the card appeared in) lblDownloadQuestImages=Download tokens and icons used in Quest mode. lblDownloadAchievementImages=Download achievement images to really make your trophies stand out. diff --git a/forge-gui/res/languages/es-es.properties b/forge-gui/res/languages/es-es.properties index 793a7c577ee..dfeda5e8963 100644 --- a/forge-gui/res/languages/es-es.properties +++ b/forge-gui/res/languages/es-es.properties @@ -179,6 +179,7 @@ Achievements=Logros # VSubmenuDownloaders.java btnDownloadSetPics=Descargar todos los Sets de Cartas btnDownloadPics=Descargar todas las Cartas +btnDownloadPicsHQ=Descargar todas las Cartas en calidad alta (Muy lento!) btnDownloadQuestImages=Descargar Imágenes del modo Quest btnDownloadAchievementImages=Descagar Imágenes de los Logros btnReportBug=Reportar un error @@ -189,6 +190,7 @@ btnHowToPlay=Cómo jugar (Inglés) btnDownloadPrices=Descargar los precios de las cartas btnLicensing=Detalles de la licencia lblDownloadPics=Descargar la imagen de la carta por defecto para cada carta. +lblDownloadPicsHQ=Descargar la imagen en calidad alta de la carta por defecto para cada carta. lblDownloadSetPics=Descargue todas las imágenes de cada carta (una por cada set donde apareció la carta) lblDownloadQuestImages=Descarga fichas e íconos utilizados en el modo Quest. lblDownloadAchievementImages=Descarga imágenes de logros para que tus trofeos realmente destaquen. diff --git a/forge-gui/src/main/java/forge/download/GuiDownloadPicturesHQ.java b/forge-gui/src/main/java/forge/download/GuiDownloadPicturesHQ.java new file mode 100644 index 00000000000..8e3e5b95802 --- /dev/null +++ b/forge-gui/src/main/java/forge/download/GuiDownloadPicturesHQ.java @@ -0,0 +1,356 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.download; + +import forge.item.PaperCard; +import forge.model.FModel; +import forge.properties.ForgeConstants; +import forge.util.ImageUtil; + +import java.io.File; +import java.util.*; + +public class GuiDownloadPicturesHQ extends GuiDownloadService { + final Map downloads = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + Set existingSets; + ArrayList existingImages; + + @Override + public String getTitle() { + return "Download HQ Card Pictures"; + } + + @Override + protected final Map getNeededFiles() { + File f = new File(ForgeConstants.CACHE_CARD_PICS_DIR); + existingImages = new ArrayList(Arrays.asList(f.list())); + existingSets = retrieveManifestDirectory(); + + for (final PaperCard c : FModel.getMagicDb().getCommonCards().getAllCards()) { + addDLObject(c, false); + if (ImageUtil.hasBackFacePicture(c)) { + addDLObject(c, true); + } + } + + for (final PaperCard c : FModel.getMagicDb().getVariantCards().getAllCards()) { + addDLObject(c, false); + } + + // Add missing tokens to the list of things to download. + addMissingItems(downloads, ForgeConstants.IMAGE_LIST_TOKENS_FILE, ForgeConstants.CACHE_TOKEN_PICS_DIR); + + return downloads; + } + + private void addDLObject(final PaperCard c, final boolean backFace) { + final String imageKey = ImageUtil.getImageKey(c, backFace, false); + final String destPath = ForgeConstants.CACHE_CARD_PICS_DIR + imageKey + ".jpg"; + + if (existingImages.contains(imageKey + ".jpg")) { + return; + } + + if (downloads.containsKey(destPath)) { + return; + } + + String setCode = c.getEdition(); + String cardname = imageKey.replace(".full", ""); + + switch(setCode) { + case "CFX": setCode="CON"; break; + case "COM": setCode="CMD"; break; + case "FVE": setCode="V09"; break; + case "FVL": setCode="V11"; break; + case "FVR": setCode="V10"; break; + case "MED": setCode="ME1"; break; + case "MPS_AKH": setCode="AKH"; break; + case "MPS_KLD": setCode="MPS"; break; + case "MPS_RNA": setCode="MED"; break; + case "PDS": setCode="H09"; break; + case "PO2": setCode="P02"; break; + case "UGF": setCode="UGIN"; break; + } + + setCode = getManualCode(cardname, setCode); + + cardname = cardname.replace(" ", "+"); + cardname = cardname.replace("'", ""); + String scryfallurl = ForgeConstants.URL_PIC_SCRYFALL_DOWNLOAD + "named?fuzzy=" + cardname; + if(!setCode.equals("???")) scryfallurl += "&set=" + setCode.toLowerCase(); + scryfallurl += "&format=image"; + + downloads.put(destPath, scryfallurl); + } + + private String getManualCode(String cardname, String originalSetCode) { + String setCode=originalSetCode; + + switch(cardname) { + case "Daretti, Ingenious Iconoclast": + case "Elspeth, Knight-Errant": + case "Garruk, Apex Predator": + case "Jace, the Mind Sculptor": + case "Liliana, the Last Hope": + case "Nahiri, the Harbinger": + case "Nicol Bolas, Planeswalker": + case "Sarkhan Unbroken": + case "Teferi, Hero of Dominaria": + case "Tezzeret, Agent of Bolas": + case "Tezzeret the Seeker": + case "Ugin, the Spirit Dragon": setCode="MED"; break; + + case "Attrition": + case "Boil": + case "Capsize": + case "Counterbalance": + case "Daze": + case "Desolation Angel": + case "Divert": + case "Forbid": + case "Force of Will": + case "Loyal Retainers": + case "Mind Twist": + case "No Mercy": + case "Opposition": + case "Shatterstorm": + case "Slaughter Pact": + case "Stifle": + case "Sunder": + case "Worship": + case "Wrath of God": setCode="MP2"; break; + + case "Behold the Power of Destruction": + case "Choose Your Champion": + case "Dance, Pathetic Marionette": + case "Embrace My Diabolical Vision": + case "Every Hope Shall Vanish": + case "Every Last Vestige Shall Rot": + case "Evil Comes to Fruition": + case "Feed the Machine": + case "I Bask in Your Silent Awe": + case "I Call on the Ancient Magics": + case "I Delight in Your Convulsions": + case "I Know All, I See All": + case "Ignite the Cloneforge!": + case "Into the Earthen Maw": + case "Introductions Are in Order": + case "Know Naught but Fire": + case "Look Skyward and Despair": + case "May Civilization Collapse": + case "Mortal Flesh Is Weak": + case "My Crushing Masterstroke": + case "My Genius Knows No Bounds": + case "My Undead Horde Awakens": + case "My Wish Is Your Command": + case "Nature Demands an Offering": + case "Nature Shields Its Own": + case "Nothing Can Stop Me Now": + case "Only Blood Ends Your Nightmares": + case "Realms Befitting My Majesty": + case "Roots of All Evil": + case "Rotted Ones, Lay Siege": + case "Surrender Your Thoughts": + case "The Dead Shall Serve": + case "The Fate of the Flammable": + case "The Iron Guardian Stirs": + case "The Pieces Are Coming Together": + case "The Very Soil Shall Shake": + case "Tooth, Claw, and Tail": + case "Which of You Burns Brightest": + case "Your Fate Is Thrice Sealed": + case "Your Puny Minds Cannot Fathom": + case "Your Will Is Not Your Own": setCode="OARC"; break; + + case "Because I Have Willed It": + case "Behold My Grandeur": + case "Bow to My Command": + case "Choose Your Demise": + case "Delight in the Hunt": + case "Every Dream a Nightmare": + case "For Each of You, a Gift": + case "Know Evil": + case "Make Yourself Useful": + case "My Forces Are Innumerable": + case "My Laughter Echoes": + case "No One Will Hear Your Cries": + case "Pay Tribute to Me": + case "Power Without Equal": + case "The Mighty Will Fall": + case "There Is No Refuge": + case "This World Belongs to Me": + case "What's Yours Is Now Mine": + case "When Will You Learn": setCode="OE01"; break; + + case "Bloodhill Bastion": + case "Celestine Reef": + case "Chaotic Aether": + case "Choke": + case "Cliffside Market": + case "Edge of Malacol": + case "Eloren Wilds": + case "Feeding Grounds": + case "Fields of Summer": + case "Furnace Layer": + case "Glimmervoid Basin": + case "Grand Ossuary": + case "Grove of the Dreampods": + case "Hedron Fields of Agadeem": + case "Horizon Boughs": + case "Immersturm": + case "Interplanar Tunnel": + case "Isle of Vesuva": + case "Izzet Steam Maze": + case "Kharasha Foothills": + case "Kilnspire District": + case "Lair of the Ashen Idol": + case "Lethe Lake": + case "Mirrored Depths": + case "Morphic Tide": + case "Mount Keralia": + case "Mutual Epiphany": + case "Norn's Dominion": + case "Onakke Catacomb": + case "Orochi Colony": + case "Panopticon": + case "Planewide Disaster": + case "Pools of Becoming": + case "Quicksilver Sea": + case "Reality Shaping": + case "Sanctum of Serra": + case "Sea of Sand": + case "Selesnya Loft Gardens": + case "Skybreen": + case "Spatial Merging": + case "Stairs to Infinity": + case "Stronghold Furnace": + case "Talon Gates": + case "Tember City": + case "Windriddle Palaces": + case "The Aether Flues": + case "The Dark Barony": + case "The Eon Fog": + case "The Fourth Sphere": + case "The Great Forest": + case "The Hippodrome": + case "The Maelstrom": + case "The Zephyr Maze": + case "Time Distortion": + case "Trail of the Mage-Rings": + case "Truga Jungle": + case "Turri Island": + case "Undercity Reaches": setCode="OPCA"; break; + + case "Drench the Soil in Their Blood": + case "Imprison This Insolent Wretch": + case "Perhaps You've Met My Cohort": + case "Plots That Span Centuries": + case "Your Inescapable Doom": setCode="PARC"; break; + + case "Stoneforge Mystic": setCode="PGPX"; break; + + case "Birds of Paradise Avatar1": + case "Bosh, Iron Golem Avatar": + case "Braids, Conjurer Adept Avatar": + case "Chronatog Avatar": + case "Dakkon Blackblade Avatar": + case "Dauntless Escort Avatar": + case "Diamond Faerie Avatar": + case "Eight-and-a-Half-Tails Avatar": + case "Enigma Sphinx Avatar": + case "Eladamri, Lord of Leaves Avatar": + case "Elvish Champion Avatar": + case "Erhnam Djinn Avatar1": + case "Etched Oracle Avatar": + case "Fallen Angel Avatar": + case "Figure of Destiny Avatar": + case "Flametongue Kavu Avatar": + case "Frenetic Efreet Avatar": + case "Goblin Warchief Avatar1": + case "Grinning Demon Avatar1": + case "Haakon, Stromgald Scourge Avatar": + case "Heartwood Storyteller Avatar": + case "Hell's Caretaker Avatar": + case "Hermit Druid Avatar": + case "Higure, the Still Wind Avatar": + case "Ink-Eyes, Servant of Oni Avatar": + case "Jaya Ballard Avatar": + case "Jhoira of the Ghitu Avatar": + case "Karona, False God Avatar": + case "Kresh the Bloodbraided Avatar": + case "Loxodon Hierarch Avatar": + case "Lyzolda, the Blood Witch Avatar": + case "Malfegor Avatar": + case "Maralen of the Mornsong Avatar": + case "Maro Avatar": + case "Master of the Wild Hunt Avatar": + case "Mayael the Anima Avatar": + case "Mirri the Cursed Avatar": + case "Mirror Entity Avatar": + case "Momir Vig, Simic Visionary Avatar": + case "Morinfen Avatar": + case "Murderous Redcap Avatar": + case "Necropotence Avatar": + case "Nekrataal Avatar": + case "Oni of Wild Places Avatar": + case "Orcish Squatters Avatar": + case "Peacekeeper Avatar": + case "Phage the Untouchable Avatar": + case "Platinum Angel Avatar1": + case "Prodigal Sorcerer Avatar1": + case "Raksha Golden Cub Avatar": + case "Reaper King Avatar": + case "Rith, the Awakener Avatar1": + case "Royal Assassin Avatar1": + case "Rumbling Slum Avatar": + case "Sakashima the Impostor Avatar": + case "Serra Angel Avatar1": + case "Seshiro the Anointed Avatar": + case "Sisters of Stone Death Avatar": + case "Sliver Queen Avatar": + case "Squee, Goblin Nabob Avatar": + case "Stalking Tiger Avatar": + case "Stonehewer Giant Avatar": + case "Stuffy Doll Avatar": + case "Teysa, Orzhov Scion Avatar": + case "Tradewind Rider Avatar1": + case "Two-Headed Giant of Foriys Avatar": + case "Vampire Nocturnus Avatar": + case "Viridian Zealot Avatar": setCode="PMOA"; break; + + case "Nalathni Dragon": + case "Sewers of Estark": + case "Windseeker Centaur": setCode="PRM"; break; + + case "Lyna": + case "Sliver Queen, Brood Mother": + case "Takara": setCode="PVAN"; break; + + case "Goblin Hero": setCode="S99"; break; + + case "Pyrostatic Pillar": + case "Weathered Wayfarer": setCode="TD0"; break; + + case "Hero's Resolve": + case "Python": setCode="6ED"; break; + } + + return setCode; + } +} diff --git a/forge-gui/src/main/java/forge/download/GuiDownloadService.java b/forge-gui/src/main/java/forge/download/GuiDownloadService.java index c0a22f27e85..04d31f77fd8 100644 --- a/forge-gui/src/main/java/forge/download/GuiDownloadService.java +++ b/forge-gui/src/main/java/forge/download/GuiDownloadService.java @@ -32,6 +32,7 @@ import java.net.URLDecoder; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -272,7 +273,13 @@ public abstract class GuiDownloadService implements Runnable { URL imageUrl = new URL(url); HttpURLConnection conn = (HttpURLConnection) imageUrl.openConnection(p); // don't allow redirections here -- they indicate 'file not found' on the server - conn.setInstanceFollowRedirects(false); + // only allow redirections to consume Scryfall API + if(url.contains("api.scryfall.com")) { + conn.setInstanceFollowRedirects(true); + TimeUnit.MILLISECONDS.sleep(100); + } else { + conn.setInstanceFollowRedirects(false); + } conn.connect(); // if file is not found and this is a JPG, give PNG a shot... diff --git a/forge-gui/src/main/java/forge/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/properties/ForgeConstants.java index 193d2b6b6e1..44d5a6e0c5e 100644 --- a/forge-gui/src/main/java/forge/properties/ForgeConstants.java +++ b/forge-gui/src/main/java/forge/properties/ForgeConstants.java @@ -284,6 +284,8 @@ public final class ForgeConstants { public static final String URL_PIC_DOWNLOAD = URL_CARDFORGE + "/images/cards/"; public static final String URL_TOKEN_DOWNLOAD = URL_CARDFORGE + "/images/tokens/"; public static final String URL_PRICE_DOWNLOAD = URL_CARDFORGE + "/all-prices.txt"; + private static final String URL_SCRYFALL = "https://api.scryfall.com"; + public static final String URL_PIC_SCRYFALL_DOWNLOAD = URL_SCRYFALL + "/cards/"; // Constants for Display Card Identity game setting public static final String DISP_CURRENT_COLORS_ALWAYS = "Always";