From 1403b3aa37b0e6875ba98d1ff2bb4030d21ea199 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 25 Jul 2024 14:27:01 +0400 Subject: [PATCH] images: fixed Star Wars images download, added Star Wars tokens (closes #12602) --- .../card/dl/sources/CardImageSource.java | 21 +- .../card/dl/sources/CopyPasteImageSource.java | 4 - .../card/dl/sources/GrabbagImageSource.java | 803 +++++++++--------- .../card/dl/sources/ScryfallImageSource.java | 2 +- .../dl/sources/WizardCardsImageSource.java | 4 - .../card/images/DownloadPicturesService.java | 47 +- .../plugins/card/utils/CardImageUtils.java | 1 + 7 files changed, 479 insertions(+), 403 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageSource.java index 1ee0c5371cd..c353095b632 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageSource.java @@ -4,8 +4,7 @@ import mage.client.util.CardLanguage; import org.mage.plugins.card.dl.DownloadServiceInfo; import org.mage.plugins.card.images.CardDownloadData; -import java.util.ArrayList; -import java.util.List; +import java.util.*; /** * @author North, JayDi85 @@ -48,11 +47,21 @@ public interface CardImageSource { } /** - * Pause before each http request (must use for services with rate limits) - * - * @param httpImageUrl + * Timeout before each http request (must use for services with rate limits) */ - void doPause(String httpImageUrl); + default void doPause(String fullUrl) { + // nothing + } + + /** + * Set additional http headers like user agent, referer, cookies, etc + */ + default Map getHttpRequestHeaders(String fullUrl) { + Map headers = new LinkedHashMap<>(); + // TODO: add xmage name and client version here + headers.put("User-Agent", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2"); + return headers; + } default List getSupportedSets() { return new ArrayList<>(); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CopyPasteImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CopyPasteImageSource.java index 2a14b9c5483..3abdfb14891 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CopyPasteImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CopyPasteImageSource.java @@ -238,10 +238,6 @@ public enum CopyPasteImageSource implements CardImageSource { return supportedSetsCopy; } - @Override - public void doPause(String httpImageUrl) { - } - @Override public boolean isCardImageProvided(String setCode, String cardName) { missingCards.add(setCode + "/" + cardName); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GrabbagImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GrabbagImageSource.java index 8e7c7f0bc5e..4a88376c2ae 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GrabbagImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GrabbagImageSource.java @@ -6,16 +6,22 @@ import org.mage.plugins.card.images.CardDownloadData; import java.io.IOException; import java.util.*; -import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** - * @author spjspj + * Images download source for imgur.com and direct links + * + * @author spjspj, JayDi85 */ public enum GrabbagImageSource implements CardImageSource { instance; private static final Logger LOGGER = Logger.getLogger(GrabbagImageSource.class); + private static final String IMGUR_IMAGE_URL = "https://i.imgur.com/"; + static final Pattern IMGUR_IMAGE_ID_PATTERN = Pattern.compile("imgur\\.com\\/(\\w+)"); + private static final Set supportedSets = new LinkedHashSet() { { add("SWS"); @@ -63,8 +69,13 @@ public enum GrabbagImageSource implements CardImageSource { } if (url != null) { - return new CardImageUrls(getSourceName(card, url) + url); + url = prepareFullUrl(card, url); } + + if (url != null) { + return new CardImageUrls(url); + } + return null; } @@ -74,387 +85,396 @@ public enum GrabbagImageSource implements CardImageSource { if (singleLinks != null) { return; } + + // can use: + // - direct links like https://sample.site/image.jpg + // - imgur.com links like CqmDY8V.jpeg (by image name) singleLinks = new HashMap<>(); - singleLinks.put("SWS/AAT-1", "CqmDY8V.jpg"); - singleLinks.put("SWS/Acklay of the Arena", "ESVRm6F.jpg"); - singleLinks.put("SWS/Acquire Target", "FOskB4q.jpg"); - singleLinks.put("SWS/Admiral Ackbar", "JdGpP3p.jpg"); - singleLinks.put("SWS/Adroit Hateflayer", "0gSIQ4K.jpg"); + + // lands + // it uses workaround for various art by -a,-b,-c,-d postfix + singleLinks.put("SWS/Forest-a", "LIpeeP9.jpeg"); + singleLinks.put("SWS/Forest-b", "jKwDwH7.jpeg"); + singleLinks.put("SWS/Forest-c", "CVb3582.jpeg"); + singleLinks.put("SWS/Forest-d", "q09fMW0.jpeg"); + singleLinks.put("SWS/Island-a", "GxITXBO.jpeg"); + singleLinks.put("SWS/Island-b", "vKdg4fG.jpeg"); + singleLinks.put("SWS/Island-c", "NPMxIew.jpeg"); + singleLinks.put("SWS/Island-d", "cnqFMa6.jpeg"); + singleLinks.put("SWS/Mountain-a", "MCii4g1.jpeg"); + singleLinks.put("SWS/Mountain-b", "Tb0ic31.jpeg"); + singleLinks.put("SWS/Mountain-c", "wqXTdsC.jpeg"); + singleLinks.put("SWS/Mountain-d", "9oBNCHk.jpeg"); + singleLinks.put("SWS/Plains-a", "HgXaAKh.jpeg"); + singleLinks.put("SWS/Plains-b", "Y0i7MBh.jpeg"); + singleLinks.put("SWS/Plains-c", "4grXQVd.jpeg"); + singleLinks.put("SWS/Plains-d", "kTmN8MM.jpeg"); + singleLinks.put("SWS/Swamp-a", "kBGj6vk.jpeg"); + singleLinks.put("SWS/Swamp-b", "BLJl2lf.jpeg"); + singleLinks.put("SWS/Swamp-c", "MLH5o2u.jpeg"); + singleLinks.put("SWS/Swamp-d", "Rmrv9tC.jpeg"); + + // cards + singleLinks.put("SWS/AAT-1", "CqmDY8V.jpeg"); + singleLinks.put("SWS/Acklay of the Arena", "ESVRm6F.jpeg"); + singleLinks.put("SWS/Acquire Target", "FOskB4q.jpeg"); + singleLinks.put("SWS/Admiral Ackbar", "JdGpP3p.jpeg"); + singleLinks.put("SWS/Adroit Hateflayer", "0gSIQ4K.jpeg"); singleLinks.put("SWS/Anakin Skywalker", "3pGvZZEg.png"); - singleLinks.put("SWS/Ancient Holocron", "fH2dVP5.jpg"); - singleLinks.put("SWS/Aqualish Bounty Hunter", "Wm2aKa2.jpg"); - singleLinks.put("SWS/Armed Protocol Droid", "mywdKgN.jpg"); - singleLinks.put("SWS/Arrest", "VXLnNUo.jpg"); - singleLinks.put("SWS/Asajj Ventress", "rOXSIwO.jpg"); - singleLinks.put("SWS/AT-ST", "9sMcy3C.jpg"); + singleLinks.put("SWS/Ancient Holocron", "fH2dVP5.jpeg"); + singleLinks.put("SWS/Aqualish Bounty Hunter", "Wm2aKa2.jpeg"); + singleLinks.put("SWS/Armed Protocol Droid", "mywdKgN.jpeg"); + singleLinks.put("SWS/Arrest", "VXLnNUo.jpeg"); + singleLinks.put("SWS/Asajj Ventress", "rOXSIwO.jpeg"); + singleLinks.put("SWS/AT-ST", "9sMcy3C.jpeg"); singleLinks.put("SWS/Aurra Sing, Bane of Jedi", "VgbndqZ.png"); - singleLinks.put("SWS/A-Wing", "4TaYoRO.jpg"); - singleLinks.put("SWS/Bantha Herd", "9rLPE2a.jpg"); - singleLinks.put("SWS/Bathe in Bacta", "sPynQAZ.jpg"); - singleLinks.put("SWS/Battle Tactics", "zoon1p4.jpg"); - singleLinks.put("SWS/Bib Fortuna", "AqAmOEw.jpg"); - singleLinks.put("SWS/Black Market Dealer", "EJpIxna.jpg"); - singleLinks.put("SWS/Blind Worship", "GonJyeF.jpg"); - singleLinks.put("SWS/Boba Fett", "XE83Ks7.jpg"); - singleLinks.put("SWS/Bossk", "m91vUdJ.jpg"); - singleLinks.put("SWS/Bounty Collector", "GHHxvb0.jpg"); - singleLinks.put("SWS/Bounty Sniper", "ANTNrsS.jpg"); - singleLinks.put("SWS/Bounty Spotter", "aB6LAZs.jpg"); - singleLinks.put("SWS/Bull Rancor", "eG4mJ7o.jpg"); - singleLinks.put("SWS/C-3PO and R2D2", "RTv4ikx.jpg"); - singleLinks.put("SWS/Cantina Band", "PqMQP0o.jpg"); - singleLinks.put("SWS/Capture", "jxoTOyC.jpg"); - singleLinks.put("SWS/Carbonite Chamber", "rqEr1gm.jpg"); - singleLinks.put("SWS/Chewbacca", "D3D5T42.jpg"); - singleLinks.put("SWS/Chief Chirpa", "Gx3hLsg.jpg"); - singleLinks.put("SWS/Cloaking Device", "Vtz1NZU.jpg"); - singleLinks.put("SWS/Commander Cody", "9PGV2pV.jpg"); - singleLinks.put("SWS/Condemn", "36yejT2.jpg"); - singleLinks.put("SWS/Corellian Corvette", "j8uPQDY.jpg"); - singleLinks.put("SWS/Crossfire", "Iz9OdPh.jpg"); - singleLinks.put("SWS/Cruelty of the Sith", "q3WIYAt.jpg"); - singleLinks.put("SWS/Cunning Abduction", "CueTNo7.jpg"); - singleLinks.put("SWS/Dagobah Maw Slug", "SqmdUMp.jpg"); - singleLinks.put("SWS/Dark Apprenticeship", "yf5MthH.jpg"); - singleLinks.put("SWS/Dark Decision", "2HB5lYN.jpg"); - singleLinks.put("SWS/Dark Trooper", "atKdUTA.jpg"); - singleLinks.put("SWS/Darth Maul", "EwC1e1Q.jpg"); + singleLinks.put("SWS/A-Wing", "4TaYoRO.jpeg"); + singleLinks.put("SWS/Bantha Herd", "9rLPE2a.jpeg"); + singleLinks.put("SWS/Bathe in Bacta", "sPynQAZ.jpeg"); + singleLinks.put("SWS/Battle Tactics", "zoon1p4.jpeg"); + singleLinks.put("SWS/Bib Fortuna", "AqAmOEw.jpeg"); + singleLinks.put("SWS/Black Market Dealer", "EJpIxna.jpeg"); + singleLinks.put("SWS/Blind Worship", "GonJyeF.jpeg"); + singleLinks.put("SWS/Boba Fett", "XE83Ks7.jpeg"); + singleLinks.put("SWS/Bossk", "m91vUdJ.jpeg"); + singleLinks.put("SWS/Bounty Collector", "GHHxvb0.jpeg"); + singleLinks.put("SWS/Bounty Sniper", "ANTNrsS.jpeg"); + singleLinks.put("SWS/Bounty Spotter", "aB6LAZs.jpeg"); + singleLinks.put("SWS/Bull Rancor", "eG4mJ7o.jpeg"); + singleLinks.put("SWS/C-3PO and R2D2", "RTv4ikx.jpeg"); + singleLinks.put("SWS/Cantina Band", "PqMQP0o.jpeg"); + singleLinks.put("SWS/Capture", "jxoTOyC.jpeg"); + singleLinks.put("SWS/Carbonite Chamber", "rqEr1gm.jpeg"); + singleLinks.put("SWS/Chewbacca", "D3D5T42.jpeg"); + singleLinks.put("SWS/Chief Chirpa", "Gx3hLsg.jpeg"); + singleLinks.put("SWS/Cloaking Device", "Vtz1NZU.jpeg"); + singleLinks.put("SWS/Commander Cody", "9PGV2pV.jpeg"); + singleLinks.put("SWS/Condemn", "36yejT2.jpeg"); + singleLinks.put("SWS/Corellian Corvette", "j8uPQDY.jpeg"); + singleLinks.put("SWS/Crossfire", "Iz9OdPh.jpeg"); + singleLinks.put("SWS/Cruelty of the Sith", "q3WIYAt.jpeg"); + singleLinks.put("SWS/Cunning Abduction", "CueTNo7.jpeg"); + singleLinks.put("SWS/Dagobah Maw Slug", "SqmdUMp.jpeg"); + singleLinks.put("SWS/Dark Apprenticeship", "yf5MthH.jpeg"); + singleLinks.put("SWS/Dark Decision", "2HB5lYN.jpeg"); + singleLinks.put("SWS/Dark Trooper", "atKdUTA.jpeg"); + singleLinks.put("SWS/Darth Maul", "EwC1e1Q.jpeg"); singleLinks.put("SWS/Darth Sidious, Sith Lord", "UYk3KnH.png"); singleLinks.put("SWS/Darth Tyranus, Count of Serenno", "AXUfNuO.png"); singleLinks.put("SWS/Darth Vader", "3pGvZZE.png"); - singleLinks.put("SWS/Death Trooper", "j7lWmPJ.jpg"); - singleLinks.put("SWS/Deploy The Troops", "QtcN0qV.jpg"); - singleLinks.put("SWS/Doom Blade", "cSuxWUr.jpg"); - singleLinks.put("SWS/Droid Commando", "HkKiaBQ.jpg"); - singleLinks.put("SWS/Droid Factory", "34L3ykD.jpg"); - singleLinks.put("SWS/Droid Foundry", "qYijxSk.jpg"); - singleLinks.put("SWS/Droideka", "BXN7t1i.jpg"); - singleLinks.put("SWS/Drone Holocron", "cHzqK4v.jpg"); - singleLinks.put("SWS/Echo Base Commando", "AdLjV4Y.jpg"); - singleLinks.put("SWS/EMP Blast", "Y0JWgRO.jpg"); - singleLinks.put("SWS/Escape Pod", "vj8gQ1u.jpg"); - singleLinks.put("SWS/Ewok Ambush", "219aufH.jpg"); - singleLinks.put("SWS/Ewok Firedancers", "DFAB3h4.jpg"); - singleLinks.put("SWS/Ewok Village", "rgQevhZ.jpg"); - singleLinks.put("SWS/Exogorth", "cS6fq3u.jpg"); - singleLinks.put("SWS/Ferocity of the Underworld", "lTqtVab.jpg"); - singleLinks.put("SWS/Flames of Remembrance", "WAKhi9i.jpg"); - singleLinks.put("SWS/Force Choke", "Uu1QUf9.jpg"); - singleLinks.put("SWS/Force Denial", "qwYGiUg.jpg"); - singleLinks.put("SWS/Force Drain", "prHdDXa.jpg"); - singleLinks.put("SWS/Force Healing", "kDGRFoj.jpg"); - singleLinks.put("SWS/Force Lightning", "DhAE9lZ.jpg"); - singleLinks.put("SWS/Force Mastery", "XPCWaP8.jpg"); - singleLinks.put("SWS/Force Pull", "rWWfkhX.jpg"); - singleLinks.put("SWS/Force Push", "aN8n4sk.jpg"); - singleLinks.put("SWS/Force Reflex", "RIlvXTz.jpg"); - singleLinks.put("SWS/Force Scream", "EsagOnR.jpg"); - singleLinks.put("SWS/Force Spark", "14MOM1y.jpg"); - singleLinks.put("SWS/Forest-a", "LIpeeP9.jpg"); - singleLinks.put("SWS/Forest-b", "jKwDwH7.jpg"); - singleLinks.put("SWS/Forest-c", "CVb3582.jpg"); - singleLinks.put("SWS/Forest-d", "q09fMW0.jpg"); - singleLinks.put("SWS/Fulfill Contract", "FtLMpHK.jpg"); - singleLinks.put("SWS/Gamorrean Prison Guard", "4dgOMPA.jpg"); - singleLinks.put("SWS/General Grievous", "tRLM8Hz.jpg"); - singleLinks.put("SWS/Gifted Initiate", "NDePdLv.jpg"); - singleLinks.put("SWS/Grand Moff Tarkin", "QXq1V40.jpg"); - singleLinks.put("SWS/Greater Krayt Dragon", "dzIiXXg.jpg"); - singleLinks.put("SWS/Greedo", "IRKwsX0.jpg"); - singleLinks.put("SWS/Gundark", "zLxfLM8.jpg"); - singleLinks.put("SWS/Gungan Captain", "1Q4DNWh.jpg"); - singleLinks.put("SWS/Han Solo", "G0Awota.jpg"); - singleLinks.put("SWS/Hazard Trooper", "ZOutamG.jpg"); - singleLinks.put("SWS/Head Hunting", "7OT1bGZ.jpg"); - singleLinks.put("SWS/Heavy Trooper", "HhZWs2N.jpg"); - singleLinks.put("SWS/Hot Pursuit", "ih1GT5Z.jpg"); - singleLinks.put("SWS/Hungry Dragonsnake", "23v7RTm.jpg"); - singleLinks.put("SWS/Hunt to Extinction", "3eJyfzZ.jpg"); - singleLinks.put("SWS/Hutt Crime Lord", "NAzK7Hp.jpg"); - singleLinks.put("SWS/Hutt Palace", "HEb2JN5.jpg"); - singleLinks.put("SWS/IG-88B", "YZUZJC8.jpg"); - singleLinks.put("SWS/Images of the Past", "sOXEk4Q.jpg"); - singleLinks.put("SWS/Imperial Gunner", "9KpZ8AX.jpg"); - singleLinks.put("SWS/Impulsive Wager", "lLutRRs.jpg"); - singleLinks.put("SWS/Insatiable Rakghoul", "IYqBnTK.jpg"); - singleLinks.put("SWS/Interrogation", "kI2bIbo.jpg"); - singleLinks.put("SWS/Ion Cannon", "Tb546IK.jpg"); - singleLinks.put("SWS/Iron Fist of the Empire", "9Ui7NRn.jpg"); - singleLinks.put("SWS/Island-a", "GxITXBO.jpg"); - singleLinks.put("SWS/Island-b", "vKdg4fG.jpg"); - singleLinks.put("SWS/Island-c", "NPMxIew.jpg"); - singleLinks.put("SWS/Island-d", "cnqFMa6.jpg"); - singleLinks.put("SWS/Ithorian Initiate", "2RYpp5o.jpg"); - singleLinks.put("SWS/Jabba the Hutt", "QMz0sKa.jpg"); - singleLinks.put("SWS/Jango Fett", "nEnspQP.jpg"); - singleLinks.put("SWS/Jar Jar Binks", "GCnx72b.jpg"); - singleLinks.put("SWS/Jar'Kai Battle Stance", "GLavgj7.jpg"); - singleLinks.put("SWS/Jedi Battle Healer", "RyIJON5.jpg"); - singleLinks.put("SWS/Jedi Battle Mage", "V9qHRGq.jpg"); - singleLinks.put("SWS/Jedi Battle Sage", "sZVlGWE.jpg"); - singleLinks.put("SWS/Jedi Enclave", "FlibBhx.jpg"); - singleLinks.put("SWS/Jedi Holocron", "ojbt2av.jpg"); - singleLinks.put("SWS/Jedi Inquirer", "ghFQA76.jpg"); - singleLinks.put("SWS/Jedi Instructor", "IwEpVkz.jpg"); - singleLinks.put("SWS/Jedi Knight", "VXNHpZs.jpg"); - singleLinks.put("SWS/Jedi Mind Trick", "aaVfgSA.jpg"); - singleLinks.put("SWS/Jedi Sentinel", "cae3O1s.jpg"); - singleLinks.put("SWS/Jedi Starfighter", "Ta20jjD.jpg"); - singleLinks.put("SWS/Jedi Temple", "ZkOUJnM.jpg"); - singleLinks.put("SWS/Jedi Training", "7L1LWie.jpg"); - singleLinks.put("SWS/Jump Trooper", "62Pg5r4.jpg"); - singleLinks.put("SWS/Jungle Village", "3a1KN8u.jpg"); - singleLinks.put("SWS/Kamino Cloning Facility", "kwBudvf.jpg"); - singleLinks.put("SWS/Ki-Adi-Mundi", "atqNF3H.jpg"); - singleLinks.put("SWS/LAAT Gunship", "DEMXleY.jpg"); - singleLinks.put("SWS/Lando Calrissian", "jcoTjGV.jpg"); - singleLinks.put("SWS/Legacy of the Beloved", "Y0ObvQg.jpg"); - singleLinks.put("SWS/Lightning Bolt", "XSNGQYi.jpg"); - singleLinks.put("SWS/Lightsaber", "YrpWygk.jpg"); - singleLinks.put("SWS/Loyal Tauntaun", "JKDV2WK.jpg"); - singleLinks.put("SWS/Luke Skywalker", "9worW6q.jpg"); - singleLinks.put("SWS/Mace Windu", "rmTuBGT.jpg"); - singleLinks.put("SWS/Maintenance Droid", "A66IIC1.jpg"); - singleLinks.put("SWS/Maintenance Hangar", "DzCYnH3.jpg"); - singleLinks.put("SWS/Mantellian Savrip", "a9JIDEM.jpg"); - singleLinks.put("SWS/March of the Droids", "A4HIQ5V.jpg"); - singleLinks.put("SWS/Massiff Swarm", "0qRPfbC.jpg"); - singleLinks.put("SWS/Might of the Wild", "eNXOdxp.jpg"); - singleLinks.put("SWS/Millennium Falcon", "aZ6sIn2.jpg"); - singleLinks.put("SWS/Miraculous Recovery", "DH6Cei8.jpg"); - singleLinks.put("SWS/Moisture Farm", "S6kJHtL.jpg"); - singleLinks.put("SWS/Mon Calamari Cruiser", "ZHdTV7p.jpg"); - singleLinks.put("SWS/Mon Calamari Initiate", "GBuXdGP.jpg"); - singleLinks.put("SWS/Mountain-a", "MCii4g1.jpg"); - singleLinks.put("SWS/Mountain-b", "Tb0ic31.jpg"); - singleLinks.put("SWS/Mountain-c", "wqXTdsC.jpg"); - singleLinks.put("SWS/Mountain-d", "9oBNCHk.jpg"); - singleLinks.put("SWS/N-1 Starfighter", "UH3qd7x.jpg"); - singleLinks.put("SWS/Nebulon-B Frigate", "F0yIR08.jpg"); - singleLinks.put("SWS/Neophyte Hateflayer", "Has2AIW.jpg"); - singleLinks.put("SWS/Nerf Herder", "VUX0LHV.jpg"); - singleLinks.put("SWS/Nexu Stalker", "E1xxHe1.jpg"); - singleLinks.put("SWS/Nightspider", "H1po0uV.jpg"); - singleLinks.put("SWS/No Contest", "6SwC9ri.jpg"); - singleLinks.put("SWS/Novice Bounty Hunter", "WfNSsLY.jpg"); - singleLinks.put("SWS/Nute Gunray", "UZg12DD.jpg"); + singleLinks.put("SWS/Death Trooper", "j7lWmPJ.jpeg"); + singleLinks.put("SWS/Deploy The Troops", "QtcN0qV.jpeg"); + singleLinks.put("SWS/Doom Blade", "cSuxWUr.jpeg"); + singleLinks.put("SWS/Droid Commando", "HkKiaBQ.jpeg"); + singleLinks.put("SWS/Droid Factory", "34L3ykD.jpeg"); + singleLinks.put("SWS/Droid Foundry", "qYijxSk.jpeg"); + singleLinks.put("SWS/Droideka", "BXN7t1i.jpeg"); + singleLinks.put("SWS/Drone Holocron", "cHzqK4v.jpeg"); + singleLinks.put("SWS/Echo Base Commando", "AdLjV4Y.jpeg"); + singleLinks.put("SWS/EMP Blast", "Y0JWgRO.jpeg"); + singleLinks.put("SWS/Escape Pod", "vj8gQ1u.jpeg"); + singleLinks.put("SWS/Ewok Ambush", "219aufH.jpeg"); + singleLinks.put("SWS/Ewok Firedancers", "DFAB3h4.jpeg"); + singleLinks.put("SWS/Ewok Village", "rgQevhZ.jpeg"); + singleLinks.put("SWS/Exogorth", "cS6fq3u.jpeg"); + singleLinks.put("SWS/Ferocity of the Underworld", "lTqtVab.jpeg"); + singleLinks.put("SWS/Flames of Remembrance", "WAKhi9i.jpeg"); + singleLinks.put("SWS/Force Choke", "Uu1QUf9.jpeg"); + singleLinks.put("SWS/Force Denial", "qwYGiUg.jpeg"); + singleLinks.put("SWS/Force Drain", "prHdDXa.jpeg"); + singleLinks.put("SWS/Force Healing", "kDGRFoj.jpeg"); + singleLinks.put("SWS/Force Lightning", "DhAE9lZ.jpeg"); + singleLinks.put("SWS/Force Mastery", "XPCWaP8.jpeg"); + singleLinks.put("SWS/Force Pull", "rWWfkhX.jpeg"); + singleLinks.put("SWS/Force Push", "aN8n4sk.jpeg"); + singleLinks.put("SWS/Force Reflex", "RIlvXTz.jpeg"); + singleLinks.put("SWS/Force Scream", "EsagOnR.jpeg"); + singleLinks.put("SWS/Force Spark", "14MOM1y.jpeg"); + singleLinks.put("SWS/Fulfill Contract", "FtLMpHK.jpeg"); + singleLinks.put("SWS/Gamorrean Prison Guard", "4dgOMPA.jpeg"); + singleLinks.put("SWS/General Grievous", "tRLM8Hz.jpeg"); + singleLinks.put("SWS/Gifted Initiate", "NDePdLv.jpeg"); + singleLinks.put("SWS/Grand Moff Tarkin", "QXq1V40.jpeg"); + singleLinks.put("SWS/Greater Krayt Dragon", "dzIiXXg.jpeg"); + singleLinks.put("SWS/Greedo", "IRKwsX0.jpeg"); + singleLinks.put("SWS/Gundark", "zLxfLM8.jpeg"); + singleLinks.put("SWS/Gungan Captain", "1Q4DNWh.jpeg"); + singleLinks.put("SWS/Han Solo", "G0Awota.jpeg"); + singleLinks.put("SWS/Hazard Trooper", "ZOutamG.jpeg"); + singleLinks.put("SWS/Head Hunting", "7OT1bGZ.jpeg"); + singleLinks.put("SWS/Heavy Trooper", "HhZWs2N.jpeg"); + singleLinks.put("SWS/Hot Pursuit", "ih1GT5Z.jpeg"); + singleLinks.put("SWS/Hungry Dragonsnake", "23v7RTm.jpeg"); + singleLinks.put("SWS/Hunt to Extinction", "3eJyfzZ.jpeg"); + singleLinks.put("SWS/Hutt Crime Lord", "NAzK7Hp.jpeg"); + singleLinks.put("SWS/Hutt Palace", "HEb2JN5.jpeg"); + singleLinks.put("SWS/IG-88B", "YZUZJC8.jpeg"); + singleLinks.put("SWS/Images of the Past", "sOXEk4Q.jpeg"); + singleLinks.put("SWS/Imperial Gunner", "9KpZ8AX.jpeg"); + singleLinks.put("SWS/Impulsive Wager", "lLutRRs.jpeg"); + singleLinks.put("SWS/Insatiable Rakghoul", "IYqBnTK.jpeg"); + singleLinks.put("SWS/Interrogation", "kI2bIbo.jpeg"); + singleLinks.put("SWS/Ion Cannon", "Tb546IK.jpeg"); + singleLinks.put("SWS/Iron Fist of the Empire", "9Ui7NRn.jpeg"); + singleLinks.put("SWS/Ithorian Initiate", "2RYpp5o.jpeg"); + singleLinks.put("SWS/Jabba the Hutt", "QMz0sKa.jpeg"); + singleLinks.put("SWS/Jango Fett", "nEnspQP.jpeg"); + singleLinks.put("SWS/Jar Jar Binks", "GCnx72b.jpeg"); + singleLinks.put("SWS/Jar'Kai Battle Stance", "GLavgj7.jpeg"); + singleLinks.put("SWS/Jedi Battle Healer", "RyIJON5.jpeg"); + singleLinks.put("SWS/Jedi Battle Mage", "V9qHRGq.jpeg"); + singleLinks.put("SWS/Jedi Battle Sage", "sZVlGWE.jpeg"); + singleLinks.put("SWS/Jedi Enclave", "FlibBhx.jpeg"); + singleLinks.put("SWS/Jedi Holocron", "ojbt2av.jpeg"); + singleLinks.put("SWS/Jedi Inquirer", "ghFQA76.jpeg"); + singleLinks.put("SWS/Jedi Instructor", "IwEpVkz.jpeg"); + singleLinks.put("SWS/Jedi Knight", "VXNHpZs.jpeg"); + singleLinks.put("SWS/Jedi Mind Trick", "aaVfgSA.jpeg"); + singleLinks.put("SWS/Jedi Sentinel", "cae3O1s.jpeg"); + singleLinks.put("SWS/Jedi Starfighter", "Ta20jjD.jpeg"); + singleLinks.put("SWS/Jedi Temple", "ZkOUJnM.jpeg"); + singleLinks.put("SWS/Jedi Training", "7L1LWie.jpeg"); + singleLinks.put("SWS/Jump Trooper", "62Pg5r4.jpeg"); + singleLinks.put("SWS/Jungle Village", "3a1KN8u.jpeg"); + singleLinks.put("SWS/Kamino Cloning Facility", "kwBudvf.jpeg"); + singleLinks.put("SWS/Ki-Adi-Mundi", "atqNF3H.jpeg"); + singleLinks.put("SWS/LAAT Gunship", "DEMXleY.jpeg"); + singleLinks.put("SWS/Lando Calrissian", "jcoTjGV.jpeg"); + singleLinks.put("SWS/Legacy of the Beloved", "Y0ObvQg.jpeg"); + singleLinks.put("SWS/Lightning Bolt", "XSNGQYi.jpeg"); + singleLinks.put("SWS/Lightsaber", "YrpWygk.jpeg"); + singleLinks.put("SWS/Loyal Tauntaun", "JKDV2WK.jpeg"); + singleLinks.put("SWS/Luke Skywalker", "9worW6q.jpeg"); + singleLinks.put("SWS/Mace Windu", "rmTuBGT.jpeg"); + singleLinks.put("SWS/Maintenance Droid", "A66IIC1.jpeg"); + singleLinks.put("SWS/Maintenance Hangar", "DzCYnH3.jpeg"); + singleLinks.put("SWS/Mantellian Savrip", "a9JIDEM.jpeg"); + singleLinks.put("SWS/March of the Droids", "A4HIQ5V.jpeg"); + singleLinks.put("SWS/Massiff Swarm", "0qRPfbC.jpeg"); + singleLinks.put("SWS/Might of the Wild", "eNXOdxp.jpeg"); + singleLinks.put("SWS/Millennium Falcon", "aZ6sIn2.jpeg"); + singleLinks.put("SWS/Miraculous Recovery", "DH6Cei8.jpeg"); + singleLinks.put("SWS/Moisture Farm", "S6kJHtL.jpeg"); + singleLinks.put("SWS/Mon Calamari Cruiser", "ZHdTV7p.jpeg"); + singleLinks.put("SWS/Mon Calamari Initiate", "GBuXdGP.jpeg"); + singleLinks.put("SWS/N-1 Starfighter", "UH3qd7x.jpeg"); + singleLinks.put("SWS/Nebulon-B Frigate", "F0yIR08.jpeg"); + singleLinks.put("SWS/Neophyte Hateflayer", "Has2AIW.jpeg"); + singleLinks.put("SWS/Nerf Herder", "VUX0LHV.jpeg"); + singleLinks.put("SWS/Nexu Stalker", "E1xxHe1.jpeg"); + singleLinks.put("SWS/Nightspider", "H1po0uV.jpeg"); + singleLinks.put("SWS/No Contest", "6SwC9ri.jpeg"); + singleLinks.put("SWS/Novice Bounty Hunter", "WfNSsLY.jpeg"); + singleLinks.put("SWS/Nute Gunray", "UZg12DD.jpeg"); singleLinks.put("SWS/Obi-Wan Kenobi", "BHkbjdL.png"); - singleLinks.put("SWS/Open Season", "E4iM90K.jpg"); - singleLinks.put("SWS/Orbital Bombardment", "Ov1mB3A.jpg"); - singleLinks.put("SWS/Order 66", "jZituQQ.jpg"); - singleLinks.put("SWS/Ortolan Keyboardist", "dYDgEUB.jpg"); - singleLinks.put("SWS/Outer Rim Slaver", "xq8ozqq.jpg"); - singleLinks.put("SWS/Outlaw Holocron", "mWbyX78.jpg"); - singleLinks.put("SWS/Personal Energy Shield", "v6TGLne.jpg"); - singleLinks.put("SWS/Plains-a", "HgXaAKh.jpg"); - singleLinks.put("SWS/Plains-b", "Y0i7MBh.jpg"); - singleLinks.put("SWS/Plains-c", "4grXQVd.jpg"); - singleLinks.put("SWS/Plains-d", "kTmN8MM.jpg"); - singleLinks.put("SWS/Plo Koon", "dDNi8CV.jpg"); - singleLinks.put("SWS/Precipice of Mortis", "TRAPT86.jpg"); - singleLinks.put("SWS/Predator's Strike", "pcBoUny.jpg"); - singleLinks.put("SWS/Preordain", "u8nSNQW.jpg"); - singleLinks.put("SWS/Primal Instinct", "bInouYH.jpg"); - singleLinks.put("SWS/Princess Leia", "pnHyPWg.jpg"); - singleLinks.put("SWS/Probe Droid", "0r84uUM.jpg"); - singleLinks.put("SWS/Qui-Gon Jinn", "7DdumG2.jpg"); - singleLinks.put("SWS/Raging Reek", "9maXaMR.jpg"); - singleLinks.put("SWS/Rallying Fire", "wtaTlhd.jpg"); - singleLinks.put("SWS/Ravenous Wampa", "rHDaKDD.jpg"); - singleLinks.put("SWS/Regression", "j5z0TOE.jpg"); - singleLinks.put("SWS/Republic Frigate", "LzNpjxP.jpg"); - singleLinks.put("SWS/Repurpose", "BvRMy3f.jpg"); - singleLinks.put("SWS/Revenge", "xkKzMRX.jpg"); - singleLinks.put("SWS/Riding Ronto", "xECBi7G.jpg"); - singleLinks.put("SWS/Rocket Trooper", "94wUTH5.jpg"); - singleLinks.put("SWS/Rogue's Passage", "UunpJPZ.jpg"); - singleLinks.put("SWS/Rule of two", "wNqZWLJ.jpg"); - singleLinks.put("SWS/Rumination", "nSD3UHQ.jpg"); - singleLinks.put("SWS/Rumor Monger", "wSN6H6v.jpg"); - singleLinks.put("SWS/Sabacc Game", "qIcFb3U.jpg"); - singleLinks.put("SWS/Salvage Squad", "ThYSxmD.jpg"); - singleLinks.put("SWS/Sand Trooper", "ysfpyL8.jpg"); + singleLinks.put("SWS/Open Season", "E4iM90K.jpeg"); + singleLinks.put("SWS/Orbital Bombardment", "Ov1mB3A.jpeg"); + singleLinks.put("SWS/Order 66", "jZituQQ.jpeg"); + singleLinks.put("SWS/Ortolan Keyboardist", "dYDgEUB.jpeg"); + singleLinks.put("SWS/Outer Rim Slaver", "xq8ozqq.jpeg"); + singleLinks.put("SWS/Outlaw Holocron", "mWbyX78.jpeg"); + singleLinks.put("SWS/Personal Energy Shield", "v6TGLne.jpeg"); + singleLinks.put("SWS/Plo Koon", "dDNi8CV.jpeg"); + singleLinks.put("SWS/Precipice of Mortis", "TRAPT86.jpeg"); + singleLinks.put("SWS/Predator's Strike", "pcBoUny.jpeg"); + singleLinks.put("SWS/Preordain", "u8nSNQW.jpeg"); + singleLinks.put("SWS/Primal Instinct", "bInouYH.jpeg"); + singleLinks.put("SWS/Princess Leia", "pnHyPWg.jpeg"); + singleLinks.put("SWS/Probe Droid", "0r84uUM.jpeg"); + singleLinks.put("SWS/Qui-Gon Jinn", "7DdumG2.jpeg"); + singleLinks.put("SWS/Raging Reek", "9maXaMR.jpeg"); + singleLinks.put("SWS/Rallying Fire", "wtaTlhd.jpeg"); + singleLinks.put("SWS/Ravenous Wampa", "rHDaKDD.jpeg"); + singleLinks.put("SWS/Regression", "j5z0TOE.jpeg"); + singleLinks.put("SWS/Republic Frigate", "LzNpjxP.jpeg"); + singleLinks.put("SWS/Repurpose", "BvRMy3f.jpeg"); + singleLinks.put("SWS/Revenge", "xkKzMRX.jpeg"); + singleLinks.put("SWS/Riding Ronto", "xECBi7G.jpeg"); + singleLinks.put("SWS/Rocket Trooper", "94wUTH5.jpeg"); + singleLinks.put("SWS/Rogue's Passage", "UunpJPZ.jpeg"); + singleLinks.put("SWS/Rule of two", "wNqZWLJ.jpeg"); + singleLinks.put("SWS/Rumination", "nSD3UHQ.jpeg"); + singleLinks.put("SWS/Rumor Monger", "wSN6H6v.jpeg"); + singleLinks.put("SWS/Sabacc Game", "qIcFb3U.jpeg"); + singleLinks.put("SWS/Salvage Squad", "ThYSxmD.jpeg"); + singleLinks.put("SWS/Sand Trooper", "ysfpyL8.jpeg"); singleLinks.put("SWS/Sarlacc Pit", "N4dcnln.png"); - singleLinks.put("SWS/Scout the Perimeter", "2cObUbz.jpg"); - singleLinks.put("SWS/Scout Trooper", "9RAY4U1.jpg"); - singleLinks.put("SWS/Security Droid", "dzy8m4v.jpg"); - singleLinks.put("SWS/Senator Bail Organa", "BRkUuYU.jpg"); - singleLinks.put("SWS/Senator Lott Dod", "yYQtXZo.jpg"); + singleLinks.put("SWS/Scout the Perimeter", "2cObUbz.jpeg"); + singleLinks.put("SWS/Scout Trooper", "9RAY4U1.jpeg"); + singleLinks.put("SWS/Security Droid", "dzy8m4v.jpeg"); + singleLinks.put("SWS/Senator Bail Organa", "BRkUuYU.jpeg"); + singleLinks.put("SWS/Senator Lott Dod", "yYQtXZo.jpeg"); singleLinks.put("SWS/Senator Onaconda Farr", "oPez77z.png"); - singleLinks.put("SWS/Senator Padme Amidala", "287deD9.jpg"); - singleLinks.put("SWS/Senator Passel Argente", "51qpnaE.jpg"); - singleLinks.put("SWS/Shaak Herd", "PtnZD0I.jpg"); - singleLinks.put("SWS/Shadow Trooper", "09NAiGa.jpg"); - singleLinks.put("SWS/Shock Trooper", "UVNOxMR.jpg"); - singleLinks.put("SWS/Show of Dominance", "ru2D3Qp.jpg"); - singleLinks.put("SWS/Sith Assassin", "Nt8WUCj.jpg"); - singleLinks.put("SWS/Sith Citadel", "bBCbK30.jpg"); - singleLinks.put("SWS/Sith Evoker", "jwRzCEB.jpg"); - singleLinks.put("SWS/Sith Holocron", "07Ufx5X.jpg"); - singleLinks.put("SWS/Sith Inquisitor", "NivI1E5.jpg"); - singleLinks.put("SWS/Sith Lord", "lWBfQoA.jpg"); - singleLinks.put("SWS/Sith Magic", "cDPFeve.jpg"); - singleLinks.put("SWS/Sith Manipulator", "Q7CIvyz.jpg"); - singleLinks.put("SWS/Sith Marauder", "9zZUSJW.jpg"); - singleLinks.put("SWS/Sith Mindseer", "Lmps3oO.jpg"); - singleLinks.put("SWS/Sith Ravager", "nl9Dp41.jpg"); - singleLinks.put("SWS/Sith Ruins", "oSqiYyO.jpg"); - singleLinks.put("SWS/Sith Sorcerer", "Pq37iop.jpg"); - singleLinks.put("SWS/Sith Thoughtseeker", "YzIY1di.jpg"); - singleLinks.put("SWS/Slave I", "QUGpxlb.jpg"); - singleLinks.put("SWS/Smash to Smithereens", "UlzDZWp.jpg"); - singleLinks.put("SWS/Snow Trooper", "28Jp5JL.jpg"); - singleLinks.put("SWS/Speeder Trooper", "BIEnTDL.jpg"); - singleLinks.put("SWS/Star Destroyer", "DYUMXHZ.jpg"); - singleLinks.put("SWS/Strike Team Commando", "783ZFsF.jpg"); - singleLinks.put("SWS/Super Battle Droid", "T8IjrKD.jpg"); - singleLinks.put("SWS/Surprise Maneuver", "uaAmzz8.jpg"); - singleLinks.put("SWS/Swamp-a", "kBGj6vk.jpg"); - singleLinks.put("SWS/Swamp-b", "BLJl2lf.jpg"); - singleLinks.put("SWS/Swamp-c", "MLH5o2u.jpg"); - singleLinks.put("SWS/Swamp-d", "Rmrv9tC.jpg"); - singleLinks.put("SWS/Swarm the Skies", "Ti1McaV.jpg"); - singleLinks.put("SWS/Syndicate Enforcer", "xZ0g2Sx.jpg"); - singleLinks.put("SWS/Tank Droid", "N4YyMje.jpg"); - singleLinks.put("SWS/Terentatek Cub", "nbmBxst.jpg"); - singleLinks.put("SWS/The Battle of Endor", "vhL6gdI.jpg"); - singleLinks.put("SWS/The Battle of Geonosis", "7Fnr2MD.jpg"); - singleLinks.put("SWS/The Battle of Hoth", "wcVJwKP.jpg"); - singleLinks.put("SWS/The Battle of Naboo", "aesSN9E.jpg"); - singleLinks.put("SWS/The Battle of Yavin", "AtGE49k.jpg"); - singleLinks.put("SWS/The Death Star", "2LjSXa9.jpg"); - singleLinks.put("SWS/TIE Bomber", "R2l7ZXQ.jpg"); - singleLinks.put("SWS/TIE Interceptor", "fYY4PUR.jpg"); - singleLinks.put("SWS/Trade Federation Battleship", "sKN9Gzv.jpg"); - singleLinks.put("SWS/Tri-Fighter", "IhwHzqT.jpg"); - singleLinks.put("SWS/Trooper Armor", "nqnFj9f.jpg"); - singleLinks.put("SWS/Trooper Commando", "PiAYXJv.jpg"); - singleLinks.put("SWS/Twi'lek Seductress", "iPhUxUV.jpg"); - singleLinks.put("SWS/Ugnaught Scrap Worker", "fuuNN3n.jpg"); - singleLinks.put("SWS/Underworld Slums", "o4CFq3x.jpg"); - singleLinks.put("SWS/Unity of the Droids", "WFAIRy3.jpg"); - singleLinks.put("SWS/Unruly Sureshot", "AHymfLc.jpg"); - singleLinks.put("SWS/Vapor Snag", "8g3q0ny.jpg"); - singleLinks.put("SWS/V-Wing", "7eThuU9.jpg"); - singleLinks.put("SWS/Weequay Beastmaster", "metAs1p.jpg"); - singleLinks.put("SWS/Wild Holocron", "adw7dFO.jpg"); - singleLinks.put("SWS/Wisdom of the Jedi", "TgTj2Dd.jpg"); - singleLinks.put("SWS/Womp Rat", "XKF79Hr.jpg"); - singleLinks.put("SWS/Wookiee Bounty Hunter", "A76UGTJ.jpg"); - singleLinks.put("SWS/Wookiee Mystic", "8DCkOVe.jpg"); - singleLinks.put("SWS/Wookiee Raidleader", "ZZTduL5.jpg"); - singleLinks.put("SWS/X-Wing", "AV1LPuZ.jpg"); + singleLinks.put("SWS/Senator Padme Amidala", "287deD9.jpeg"); + singleLinks.put("SWS/Senator Passel Argente", "51qpnaE.jpeg"); + singleLinks.put("SWS/Shaak Herd", "PtnZD0I.jpeg"); + singleLinks.put("SWS/Shadow Trooper", "09NAiGa.jpeg"); + singleLinks.put("SWS/Shock Trooper", "UVNOxMR.jpeg"); + singleLinks.put("SWS/Show of Dominance", "ru2D3Qp.jpeg"); + singleLinks.put("SWS/Sith Assassin", "Nt8WUCj.jpeg"); + singleLinks.put("SWS/Sith Citadel", "bBCbK30.jpeg"); + singleLinks.put("SWS/Sith Evoker", "jwRzCEB.jpeg"); + singleLinks.put("SWS/Sith Holocron", "07Ufx5X.jpeg"); + singleLinks.put("SWS/Sith Inquisitor", "NivI1E5.jpeg"); + singleLinks.put("SWS/Sith Lord", "lWBfQoA.jpeg"); + singleLinks.put("SWS/Sith Magic", "cDPFeve.jpeg"); + singleLinks.put("SWS/Sith Manipulator", "Q7CIvyz.jpeg"); + singleLinks.put("SWS/Sith Marauder", "9zZUSJW.jpeg"); + singleLinks.put("SWS/Sith Mindseer", "Lmps3oO.jpeg"); + singleLinks.put("SWS/Sith Ravager", "nl9Dp41.jpeg"); + singleLinks.put("SWS/Sith Ruins", "oSqiYyO.jpeg"); + singleLinks.put("SWS/Sith Sorcerer", "Pq37iop.jpeg"); + singleLinks.put("SWS/Sith Thoughtseeker", "YzIY1di.jpeg"); + singleLinks.put("SWS/Slave I", "QUGpxlb.jpeg"); + singleLinks.put("SWS/Smash to Smithereens", "UlzDZWp.jpeg"); + singleLinks.put("SWS/Snow Trooper", "28Jp5JL.jpeg"); + singleLinks.put("SWS/Speeder Trooper", "BIEnTDL.jpeg"); + singleLinks.put("SWS/Star Destroyer", "DYUMXHZ.jpeg"); + singleLinks.put("SWS/Strike Team Commando", "783ZFsF.jpeg"); + singleLinks.put("SWS/Super Battle Droid", "T8IjrKD.jpeg"); + singleLinks.put("SWS/Surprise Maneuver", "uaAmzz8.jpeg"); + singleLinks.put("SWS/Swarm the Skies", "Ti1McaV.jpeg"); + singleLinks.put("SWS/Syndicate Enforcer", "xZ0g2Sx.jpeg"); + singleLinks.put("SWS/Tank Droid", "N4YyMje.jpeg"); + singleLinks.put("SWS/Terentatek Cub", "nbmBxst.jpeg"); + singleLinks.put("SWS/The Battle of Endor", "vhL6gdI.jpeg"); + singleLinks.put("SWS/The Battle of Geonosis", "7Fnr2MD.jpeg"); + singleLinks.put("SWS/The Battle of Hoth", "wcVJwKP.jpeg"); + singleLinks.put("SWS/The Battle of Naboo", "aesSN9E.jpeg"); + singleLinks.put("SWS/The Battle of Yavin", "AtGE49k.jpeg"); + singleLinks.put("SWS/The Death Star", "2LjSXa9.jpeg"); + singleLinks.put("SWS/TIE Bomber", "R2l7ZXQ.jpeg"); + singleLinks.put("SWS/TIE Interceptor", "fYY4PUR.jpeg"); + singleLinks.put("SWS/Trade Federation Battleship", "sKN9Gzv.jpeg"); + singleLinks.put("SWS/Tri-Fighter", "IhwHzqT.jpeg"); + singleLinks.put("SWS/Trooper Armor", "nqnFj9f.jpeg"); + singleLinks.put("SWS/Trooper Commando", "PiAYXJv.jpeg"); + singleLinks.put("SWS/Twi'lek Seductress", "iPhUxUV.jpeg"); + singleLinks.put("SWS/Ugnaught Scrap Worker", "fuuNN3n.jpeg"); + singleLinks.put("SWS/Underworld Slums", "o4CFq3x.jpeg"); + singleLinks.put("SWS/Unity of the Droids", "WFAIRy3.jpeg"); + singleLinks.put("SWS/Unruly Sureshot", "AHymfLc.jpeg"); + singleLinks.put("SWS/Vapor Snag", "8g3q0ny.jpeg"); + singleLinks.put("SWS/V-Wing", "7eThuU9.jpeg"); + singleLinks.put("SWS/Weequay Beastmaster", "metAs1p.jpeg"); + singleLinks.put("SWS/Wild Holocron", "adw7dFO.jpeg"); + singleLinks.put("SWS/Wisdom of the Jedi", "TgTj2Dd.jpeg"); + singleLinks.put("SWS/Womp Rat", "XKF79Hr.jpeg"); + singleLinks.put("SWS/Wookiee Bounty Hunter", "A76UGTJ.jpeg"); + singleLinks.put("SWS/Wookiee Mystic", "8DCkOVe.jpeg"); + singleLinks.put("SWS/Wookiee Raidleader", "ZZTduL5.jpeg"); + singleLinks.put("SWS/X-Wing", "AV1LPuZ.jpeg"); singleLinks.put("SWS/Yoda, Jedi Master", "6arN1Hl.png"); - singleLinks.put("SWS/Y-Wing", "aQQ5zwA.jpg"); - singleLinks.put("SWS/Zam Wesell", "ToG0C1r.jpg"); - singleLinks.put("SWS/Astromech Droid", "v0TpHMh.jpg"); + singleLinks.put("SWS/Y-Wing", "aQQ5zwA.jpeg"); + singleLinks.put("SWS/Zam Wesell", "ToG0C1r.jpeg"); + singleLinks.put("SWS/Astromech Droid", "v0TpHMh.jpeg"); singleLinks.put("SWS/Buried Ruin", "QkmIWYg.png"); - singleLinks.put("SWS/Flame Trooper", "RkY7KFJ.jpg"); - singleLinks.put("SWS/Force Stasis", "FavLrcY.jpg"); - singleLinks.put("SWS/Salvage Trader", "qGwk7Bn.jpg"); + singleLinks.put("SWS/Flame Trooper", "RkY7KFJ.jpeg"); + singleLinks.put("SWS/Force Stasis", "FavLrcY.jpeg"); + singleLinks.put("SWS/Salvage Trader", "qGwk7Bn.jpeg"); singleLinks.put("SWS/Outer Rim Gang", "kEjKQGy.png"); singleLinks.put("SWS/Rathtar", "CYhHRqF.png"); - singleLinks.put("SWS/Riot Trooper", "PusvaQB.jpg"); - singleLinks.put("SWS/Sins of the Father", "32YHTPB.jpg"); - singleLinks.put("SWS/Upsilon-class Shuttle", "Le3F3oW.jpg"); - singleLinks.put("SWS/Finn", "TU2LI2q.jpg"); + singleLinks.put("SWS/Riot Trooper", "PusvaQB.jpeg"); + singleLinks.put("SWS/Sins of the Father", "32YHTPB.jpeg"); + singleLinks.put("SWS/Upsilon-class Shuttle", "Le3F3oW.jpeg"); + singleLinks.put("SWS/Finn", "TU2LI2q.jpeg"); singleLinks.put("SWS/General Hux", "UpWfcV6.png"); singleLinks.put("SWS/Poe Dameron", "v8i21dn.png"); singleLinks.put("SWS/Rey", "7n5ZZFA.png"); singleLinks.put("SWS/Kylo Ren", "fFzDMTz.png"); - singleLinks.put("SWS/TIE Striker", "6b5GDUQ.jpg"); - singleLinks.put("SWS/Bludgeoning Pain", "ap5k3Wl.jpg"); - singleLinks.put("SWS/Force Protection", "GrOQLHO.jpg"); - singleLinks.put("SWS/Gerrera's Revolutionary", "FQFE1Jt.jpg"); - singleLinks.put("SWS/Thermal Detonator", "gTPLM83.jpg"); - singleLinks.put("SWS/Hammerhead Corvette", "IlhOAGv.jpg"); - singleLinks.put("SWS/U-Wing", "FmoRCmG.jpg"); - singleLinks.put("SWS/Bor Gullet", "jXafYHX.jpg"); - singleLinks.put("SWS/Imperial Hovertank", "6X1wL4d.jpg"); - singleLinks.put("SWS/Occupation", "h4mmkA5.jpg"); - singleLinks.put("SWS/Resistance", "lbNhA59.jpg"); - singleLinks.put("SWS/Jyn Erso and Cassian Andor", "o0SCGiJ.jpg"); - singleLinks.put("SWS/Chirrut Imwe", "wgtXfUF.jpg"); - singleLinks.put("SWS/Director Krennic", "52PGsH5.jpg"); - singleLinks.put("SWS/Vader's Command", "7Lql6UT.jpg"); - singleLinks.put("SWS/Delay Tactic", "ubmzD1m.jpg"); - singleLinks.put("SWS/Resistance Bomber", "Sudfkd7.jpg"); - singleLinks.put("SWS/Mouse Droid", "oO0p8QE.jpg"); - singleLinks.put("SWS/First Order Dreadnought", "80pO9Cc.jpg"); - singleLinks.put("SWS/TIE Silencer", "7yeYIjX.jpg"); - singleLinks.put("SWS/Canto Bight Enforcer", "VKPQVsn.jpg"); - singleLinks.put("SWS/Cantonica Casino", "7LiSvy6.jpg"); - singleLinks.put("SWS/Fathier", "0oKquQp.jpg"); - singleLinks.put("SWS/Code Slice", "7uNASji.jpg"); - singleLinks.put("SWS/Captain Phasma", "LWujx1B.jpg"); - singleLinks.put("SWS/Force Telepathy", "e90hswX.jpg"); - singleLinks.put("SWS/Praetorian Trooper", "pjS1wyS.jpg"); - singleLinks.put("SWS/Supreme Leader Snoke", "eewWiKE.jpg"); - singleLinks.put("SWS/Sai Tok", "FVn29tT.jpg"); - singleLinks.put("SWS/Porg Nest", "8DnNZKc.jpg"); - singleLinks.put("SWS/Inspire", "7lIXhtd.jpg"); - singleLinks.put("SWS/Force Projection", "5EfOwyn.jpg"); - singleLinks.put("SWS/Luke Skywalker, the Last Jedi", "WMmQcyD.jpg"); - singleLinks.put("SWS/Vulptex", "30WeCkw.jpg"); - singleLinks.put("SWS/Glorious Charge", "yJwvKzk.jpg"); - singleLinks.put("SWS/Plains-520b", "Fx59r9J.jpg"); - singleLinks.put("SWS/Island-520a", "jIPpWp5.jpg"); - singleLinks.put("SWS/Conscription", "An01yAe.jpg"); - singleLinks.put("SWS/Afterburn", "2ydqSvT.jpg"); - singleLinks.put("SWS/Corellian Gunship", "mZdDQWH.jpg"); - singleLinks.put("SWS/Despair", "TLTddMI.jpg"); - singleLinks.put("SWS/Dryden Vos", "6LbtUzN.jpg"); - singleLinks.put("SWS/Droid Uprising", "aWuoxho.jpg"); - singleLinks.put("SWS/Gamble", "Hwzr60O.jpg"); - singleLinks.put("SWS/Han Solo, Scrumrat", "Hqj39dG.jpg"); - singleLinks.put("SWS/Mud Trooper", "af8JaDy.jpg"); - singleLinks.put("SWS/Enfys Nest", "pstVfQg.jpg"); - singleLinks.put("SWS/Kalevan Star Yacht", "nHmSizp.jpg"); - singleLinks.put("SWS/Maelstrom Blockade", "sUYT0pc.jpg"); - singleLinks.put("SWS/Range Trooper", "kXGvTkE.jpg"); - singleLinks.put("SWS/Tobias Beckett", "hzm6ilE.jpg"); - singleLinks.put("SWS/Underground Forum", "FH2pRfU.jpg"); - singleLinks.put("SWS/Chewbacca, the Beast", "Zb5TitZ.jpg"); - singleLinks.put("SWS/A Jedi's Fervor", "5MjPOpE.jpg"); - singleLinks.put("SWS/Allegiant General Pryde", "Ucithhc.jpg"); - singleLinks.put("SWS/Balance", "EMdbTBj.jpg"); - singleLinks.put("SWS/Band Together", "xc9dQaZ.jpg"); - singleLinks.put("SWS/Ben Solo", "4GXbkI3.jpg"); - singleLinks.put("SWS/Betray", "SjyF6Nq.jpg"); - singleLinks.put("SWS/Brave the Elements", "z2etu3V.jpg"); - singleLinks.put("SWS/Culling Dais", "NHRMrmo.jpg"); - singleLinks.put("SWS/Droidsmith", "HYyiEI7.jpg"); - singleLinks.put("SWS/Dyad Force Transfer", "dmyQ7Jm.jpg"); - singleLinks.put("SWS/Festival of the Ancestors", "JUPnIEr.jpg"); - singleLinks.put("SWS/First Order Jet Trooper", "wVqmWMK.jpg"); - singleLinks.put("SWS/Force Lift", "46eGWAq.jpg"); - singleLinks.put("SWS/General Organa", "YSv61yR.jpg"); - singleLinks.put("SWS/Hidden Base", "M6BR6aH.jpg"); - singleLinks.put("SWS/Hold Captive", "3dR542o.jpg"); - singleLinks.put("SWS/Holochess", "lPS1mR6.jpg"); - singleLinks.put("SWS/Knights of Ren", "vdXyRpy.jpg"); - singleLinks.put("SWS/Lightspeed Skipping", "VRquVqA.jpg"); - singleLinks.put("SWS/Luke's Lightsaber", "Ty3j9y3.jpg"); - singleLinks.put("SWS/Mimic Vat", "9bAK1LC.jpg"); - singleLinks.put("SWS/Orbak", "8RvSbhX.jpg"); - singleLinks.put("SWS/Propaganda", "TBNlxqj.jpg"); - singleLinks.put("SWS/Rey Skywalker", "dOyjNXc.jpg"); - singleLinks.put("SWS/Rey's Lightsaber", "eMzKXAP.jpg"); - singleLinks.put("SWS/Sith Eternal Lightning", "oaFkj3N.jpg"); - singleLinks.put("SWS/Sith Wayfinder", "AznLRt0.jpg"); - singleLinks.put("SWS/Swamp-e", "LrbqB3U.jpg"); - singleLinks.put("SWS/Training Droid", "JCY9KOM.jpg"); - singleLinks.put("SWS/Unpleasant Discovery", "lDKoeAu.jpg"); - singleLinks.put("SWS/Vexis", "cNQX9Ue.jpg"); - singleLinks.put("SWS/War Room", "pqQ9kzt.jpg"); - singleLinks.put("SWS/Xyston Star Destroyer", "oqbHtUC.jpg"); - singleLinks.put("SWS/Zorii Bliss", "vOyNE39.jpg"); + singleLinks.put("SWS/TIE Striker", "6b5GDUQ.jpeg"); + singleLinks.put("SWS/Bludgeoning Pain", "ap5k3Wl.jpeg"); + singleLinks.put("SWS/Force Protection", "GrOQLHO.jpeg"); + singleLinks.put("SWS/Gerrera's Revolutionary", "FQFE1Jt.jpeg"); + singleLinks.put("SWS/Thermal Detonator", "gTPLM83.jpeg"); + singleLinks.put("SWS/Hammerhead Corvette", "IlhOAGv.jpeg"); + singleLinks.put("SWS/U-Wing", "FmoRCmG.jpeg"); + singleLinks.put("SWS/Bor Gullet", "jXafYHX.jpeg"); + singleLinks.put("SWS/Imperial Hovertank", "6X1wL4d.jpeg"); + singleLinks.put("SWS/Occupation", "h4mmkA5.jpeg"); + singleLinks.put("SWS/Resistance", "lbNhA59.jpeg"); + singleLinks.put("SWS/Jyn Erso and Cassian Andor", "o0SCGiJ.jpeg"); + singleLinks.put("SWS/Chirrut Imwe", "wgtXfUF.jpeg"); + singleLinks.put("SWS/Director Krennic", "52PGsH5.jpeg"); + singleLinks.put("SWS/Vader's Command", "7Lql6UT.jpeg"); + singleLinks.put("SWS/Delay Tactic", "ubmzD1m.jpeg"); + singleLinks.put("SWS/Resistance Bomber", "Sudfkd7.jpeg"); + singleLinks.put("SWS/Mouse Droid", "oO0p8QE.jpeg"); + singleLinks.put("SWS/First Order Dreadnought", "80pO9Cc.jpeg"); + singleLinks.put("SWS/TIE Silencer", "7yeYIjX.jpeg"); + singleLinks.put("SWS/Canto Bight Enforcer", "VKPQVsn.jpeg"); + singleLinks.put("SWS/Cantonica Casino", "7LiSvy6.jpeg"); + singleLinks.put("SWS/Fathier", "0oKquQp.jpeg"); + singleLinks.put("SWS/Code Slice", "7uNASji.jpeg"); + singleLinks.put("SWS/Captain Phasma", "LWujx1B.jpeg"); + singleLinks.put("SWS/Force Telepathy", "e90hswX.jpeg"); + singleLinks.put("SWS/Praetorian Trooper", "pjS1wyS.jpeg"); + singleLinks.put("SWS/Supreme Leader Snoke", "eewWiKE.jpeg"); + singleLinks.put("SWS/Sai Tok", "FVn29tT.jpeg"); + singleLinks.put("SWS/Porg Nest", "8DnNZKc.jpeg"); + singleLinks.put("SWS/Inspire", "7lIXhtd.jpeg"); + singleLinks.put("SWS/Force Projection", "5EfOwyn.jpeg"); + singleLinks.put("SWS/Luke Skywalker, the Last Jedi", "WMmQcyD.jpeg"); + singleLinks.put("SWS/Vulptex", "30WeCkw.jpeg"); + singleLinks.put("SWS/Glorious Charge", "yJwvKzk.jpeg"); + singleLinks.put("SWS/Plains-520b", "Fx59r9J.jpeg"); + singleLinks.put("SWS/Island-520a", "jIPpWp5.jpeg"); + singleLinks.put("SWS/Conscription", "An01yAe.jpeg"); + singleLinks.put("SWS/Afterburn", "2ydqSvT.jpeg"); + singleLinks.put("SWS/Corellian Gunship", "mZdDQWH.jpeg"); + singleLinks.put("SWS/Despair", "TLTddMI.jpeg"); + singleLinks.put("SWS/Dryden Vos", "6LbtUzN.jpeg"); + singleLinks.put("SWS/Droid Uprising", "aWuoxho.jpeg"); + singleLinks.put("SWS/Gamble", "Hwzr60O.jpeg"); + singleLinks.put("SWS/Han Solo, Scrumrat", "Hqj39dG.jpeg"); + singleLinks.put("SWS/Mud Trooper", "af8JaDy.jpeg"); + singleLinks.put("SWS/Enfys Nest", "pstVfQg.jpeg"); + singleLinks.put("SWS/Kalevan Star Yacht", "nHmSizp.jpeg"); + singleLinks.put("SWS/Maelstrom Blockade", "sUYT0pc.jpeg"); + singleLinks.put("SWS/Range Trooper", "kXGvTkE.jpeg"); + singleLinks.put("SWS/Tobias Beckett", "hzm6ilE.jpeg"); + singleLinks.put("SWS/Underground Forum", "FH2pRfU.jpeg"); + singleLinks.put("SWS/Chewbacca, the Beast", "Zb5TitZ.jpeg"); + singleLinks.put("SWS/A Jedi's Fervor", "5MjPOpE.jpeg"); + singleLinks.put("SWS/Allegiant General Pryde", "Ucithhc.jpeg"); + singleLinks.put("SWS/Balance", "EMdbTBj.jpeg"); + singleLinks.put("SWS/Band Together", "xc9dQaZ.jpeg"); + singleLinks.put("SWS/Ben Solo", "4GXbkI3.jpeg"); + singleLinks.put("SWS/Betray", "SjyF6Nq.jpeg"); + singleLinks.put("SWS/Brave the Elements", "z2etu3V.jpeg"); + singleLinks.put("SWS/Culling Dais", "NHRMrmo.jpeg"); + singleLinks.put("SWS/Droidsmith", "HYyiEI7.jpeg"); + singleLinks.put("SWS/Dyad Force Transfer", "dmyQ7Jm.jpeg"); + singleLinks.put("SWS/Festival of the Ancestors", "JUPnIEr.jpeg"); + singleLinks.put("SWS/First Order Jet Trooper", "wVqmWMK.jpeg"); + singleLinks.put("SWS/Force Lift", "46eGWAq.jpeg"); + singleLinks.put("SWS/General Organa", "YSv61yR.jpeg"); + singleLinks.put("SWS/Hidden Base", "M6BR6aH.jpeg"); + singleLinks.put("SWS/Hold Captive", "3dR542o.jpeg"); + singleLinks.put("SWS/Holochess", "lPS1mR6.jpeg"); + singleLinks.put("SWS/Knights of Ren", "vdXyRpy.jpeg"); + singleLinks.put("SWS/Lightspeed Skipping", "VRquVqA.jpeg"); + singleLinks.put("SWS/Luke's Lightsaber", "Ty3j9y3.jpeg"); + singleLinks.put("SWS/Mimic Vat", "9bAK1LC.jpeg"); + singleLinks.put("SWS/Orbak", "8RvSbhX.jpeg"); + singleLinks.put("SWS/Propaganda", "TBNlxqj.jpeg"); + singleLinks.put("SWS/Rey Skywalker", "dOyjNXc.jpeg"); + singleLinks.put("SWS/Rey's Lightsaber", "eMzKXAP.jpeg"); + singleLinks.put("SWS/Sith Eternal Lightning", "oaFkj3N.jpeg"); + singleLinks.put("SWS/Sith Wayfinder", "AznLRt0.jpeg"); + singleLinks.put("SWS/Swamp-e", "LrbqB3U.jpeg"); + singleLinks.put("SWS/Training Droid", "JCY9KOM.jpeg"); + singleLinks.put("SWS/Unpleasant Discovery", "lDKoeAu.jpeg"); + singleLinks.put("SWS/Vexis", "cNQX9Ue.jpeg"); + singleLinks.put("SWS/War Room", "pqQ9kzt.jpeg"); + singleLinks.put("SWS/Xyston Star Destroyer", "oqbHtUC.jpeg"); + singleLinks.put("SWS/Zorii Bliss", "vOyNE39.jpeg"); // Emblems singleLinks.put("SWS/Emblem Obi-Wan Kenobi", "Qyc10aT.png"); singleLinks.put("SWS/Aurra Sing", "BLWbVJC.png"); singleLinks.put("SWS/Yoda", "zH0sYxg.png"); - singleLinks.put("SWS/Emblem Luke Skywalker", "kHELZDJ.jpg"); + singleLinks.put("SWS/Emblem Luke Skywalker", "kHELZDJ.jpeg"); // Tokens singleLinks.put("SWS/Ewok", "N2MvJyr.png"); @@ -467,8 +487,8 @@ public enum GrabbagImageSource implements CardImageSource { singleLinks.put("SWS/Royal Guard", "9tqE8vL.png"); singleLinks.put("SWS/Tusken Raider", "gPMiSmP.png"); singleLinks.put("SWS/Droid", "4PRrWFF.png"); - singleLinks.put("SWS/Trooper 2", "tcxvGOn.jpg"); - singleLinks.put("SWS/Porg", "HBjt1A3.jpg"); + singleLinks.put("SWS/Trooper 2", "tcxvGOn.jpeg"); + singleLinks.put("SWS/Porg", "HBjt1A3.jpeg"); } @Override @@ -497,41 +517,62 @@ public enum GrabbagImageSource implements CardImageSource { return true; } + @Override + public Map getHttpRequestHeaders(String fullUrl) { + Map headers = CardImageSource.super.getHttpRequestHeaders(fullUrl); + + if (fullUrl.startsWith(IMGUR_IMAGE_URL)) { + // imgur require page referer to download image + Matcher matcher = IMGUR_IMAGE_ID_PATTERN.matcher(fullUrl); + if (matcher.find()) { + headers.put("Host", "imgur.com"); + headers.put("Referer", "https://imgur.com/" + matcher.group(1)); + } else { + LOGGER.error("Can't find image id from url " + fullUrl); + } + } + + return headers; + } + @Override public boolean isCardSource() { return true; } - private String getSourceName(CardDownloadData card, String httpImageUrl) { - if (card.getSet().equals("MTG")) { - return "http://static.starcitygames.com/sales/cardscans/"; - } else if (card.getSet().equals("SWS")) { - return "https://i.imgur.com/"; + private String prepareFullUrl(CardDownloadData card, String url) { + if (url.startsWith("http")) { + // from direct link + return url; } else { - return "http://magiccards.info/scans/en/"; + // from imgur.com + return IMGUR_IMAGE_URL + url; } } + @Override public List getSupportedSets() { return new ArrayList<>(supportedSets); } - @Override - public void doPause(String httpImageUrl) { - if (!httpImageUrl.startsWith("/MTG")) { - try { - TimeUnit.SECONDS.sleep(2); - } catch (InterruptedException ignored) { - } - } - } - @Override public boolean isCardImageProvided(String setCode, String cardName) { if (singleLinks == null) { setupLinks(); } - return singleLinks.containsKey(setCode + "/" + cardName) || singleLinks.containsKey(setCode + "/" + cardName + "-a"); + return singleLinks.containsKey(setCode + "/" + cardName) + || singleLinks.containsKey(setCode + "/" + cardName + "-a") + || singleLinks.containsKey(setCode + "/" + cardName + "-b") + || singleLinks.containsKey(setCode + "/" + cardName + "-c") + || singleLinks.containsKey(setCode + "/" + cardName + "-d"); + } + + @Override + public boolean isTokenImageProvided(String setCode, String cardName, Integer tokenNumber) { + if (singleLinks == null) { + setupLinks(); + } + return singleLinks.containsKey(setCode + "/" + cardName); } } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java index bc2fee7c45d..ed63f9b99c0 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java @@ -335,7 +335,7 @@ public class ScryfallImageSource implements CardImageSource { } @Override - public void doPause(String httpImageUrl) { + public void doPause(String fullUrl) { waitBeforeRequest(); } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java index b32a9437f97..bacb41aa449 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java @@ -698,10 +698,6 @@ public enum WizardCardsImageSource implements CardImageSource { return currentLanguage; } - @Override - public void doPause(String httpImageUrl) { - } - @Override public List getSupportedSets() { return new ArrayList<>(supportedSets); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java index 998c75590ec..492bdce5d4b 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java @@ -16,6 +16,7 @@ import mage.remote.Connection; import mage.util.ThreadUtils; import mage.util.XMageThreadFactory; import net.java.truevfs.access.TFile; +import net.java.truevfs.access.TFileInputStream; import net.java.truevfs.access.TFileOutputStream; import net.java.truevfs.access.TVFS; import net.java.truevfs.kernel.spec.FsSyncException; @@ -24,9 +25,11 @@ import org.mage.plugins.card.dl.DownloadServiceInfo; import org.mage.plugins.card.dl.sources.*; import org.mage.plugins.card.utils.CardImageUtils; +import javax.imageio.ImageIO; import javax.swing.*; import java.awt.*; import java.awt.event.ItemEvent; +import java.awt.image.BufferedImage; import java.io.*; import java.net.*; import java.nio.file.AccessDeniedException; @@ -35,6 +38,7 @@ import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir; @@ -59,7 +63,10 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements private static final int MAX_ERRORS_COUNT_BEFORE_CANCEL = 50; - private static final int MIN_FILE_SIZE_OF_GOOD_IMAGE = 1024 * 6; // protect from wrong data save + // protect from wrong data save + // there are possible land images with small sizes, so must research content in check + private static final int MIN_FILE_SIZE_OF_GOOD_IMAGE = 1024 * 6; // broken + private static final int MIN_FILE_SIZE_OF_POSSIBLE_BAD_IMAGE = 1024 * 15; // possible broken (need content check) private final DownloadImagesDialog uiDialog; private boolean needCancel; @@ -577,6 +584,7 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements // find missing files List cardsToDownload = Collections.synchronizedList(new ArrayList<>()); + AtomicInteger badContentChecks = new AtomicInteger(); allCardsUrls.parallelStream().forEach(card -> { if (redownloadMode) { // need all cards @@ -588,13 +596,32 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements if (!file.exists()) { cardsToDownload.add(card); } else if (file.length() < MIN_FILE_SIZE_OF_GOOD_IMAGE) { + // too small, e.g. contains http error page instead image data // how-to fix: if it really downloads image data then set lower file size - logger.error("Found broken file: " + imagePath); + logger.error("Found broken file (small size): " + imagePath); cardsToDownload.add(card); + } else if (file.length() < MIN_FILE_SIZE_OF_POSSIBLE_BAD_IMAGE) { + // bad image format, e.g. contains redirected site page + badContentChecks.incrementAndGet(); + try { + try (TFileInputStream inputStream = new TFileInputStream(file)) { + BufferedImage image = ImageIO.read(inputStream); + if (image.getWidth() <= 0) { + throw new IOException("bad format"); + } + } + } catch (Exception e) { + logger.error("Found broken file (bad format): " + imagePath); + cardsToDownload.add(card); + } } } }); + if (badContentChecks.get() > 10000) { + logger.warn("Wrong code usage: too many file content checks (" + badContentChecks.get() + ") - try to decrease min file size"); + } + return Collections.synchronizedList(new ArrayList<>(cardsToDownload)); } @@ -819,18 +846,23 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements for (String currentUrl : downloadUrls) { URL url = new URL(currentUrl); - // on download cancel need to stop + // fast stop on cancel if (DownloadPicturesService.getInstance().isNeedCancel()) { return; } - // download - selectedSource.doPause(url.getPath()); + // timeout before each request + selectedSource.doPause(url.toString()); httpConn = url.openConnection(proxy); if (httpConn != null) { - httpConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2"); + // custom headers like user agent + Map headers = selectedSource.getHttpRequestHeaders(url.toString()); + for (String key : headers.keySet()) { + httpConn.setRequestProperty(key, headers.get(key)); + } + try { httpConn.connect(); } catch (SocketException e) { @@ -902,8 +934,9 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements out.write(buf, 0, len); } } - // TODO: add two faces card correction? (WTF) + // SAVE final data + // TODO: add image data check here instead use it on download dialog? if (fileTempImage.exists()) { if (!destFile.getParentFile().exists()) { destFile.getParentFile().mkdirs(); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/utils/CardImageUtils.java b/Mage.Client/src/main/java/org/mage/plugins/card/utils/CardImageUtils.java index 8fe62d690de..64cf2bd90b3 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/utils/CardImageUtils.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/utils/CardImageUtils.java @@ -230,6 +230,7 @@ public final class CardImageUtils { public static void checkAndFixImageFiles() { // search broken, temp or outdated files and delete it + // real images check is slow, so it used on images download only (not here) Path rootPath = new File(CardImageUtils.getImagesDir()).toPath(); if (!Files.exists(rootPath)) { return;