download.py 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  1. """
  2. Manage the logic of downloading the pokedex and source images.
  3. """
  4. import re
  5. import json
  6. import asyncio
  7. from pathlib import Path
  8. from dataclasses import dataclass, asdict
  9. from collections import defaultdict
  10. from aiohttp import ClientSession
  11. JS_TO_JSON = re.compile(r"\b([a-zA-Z][a-zA-Z0-9]*?):")
  12. # the dex from showdown assumes only strawberry alcremie, since
  13. # that's what's in showdown, but we might as well add the rest
  14. ALCREMIE_SWEETS = [
  15. "Strawberry", "Berry", "Love", "Star",
  16. "Clover", "Flower", "Ribbon",
  17. ]
  18. # https://bulbapedia.bulbagarden.net/wiki/List_of_Pok%C3%A9mon_with_gender_differences
  19. # there are some pokemon with notable gender diffs that the dex doesn't cover
  20. # judgement calls made arbitrarily
  21. GENDER_DIFFS = (
  22. "wobbuffet",
  23. "hippopotas", "hippowdon",
  24. "unfezant",
  25. "frillish", "jellicent",
  26. "pyroar",
  27. # meowstic, indeedee, basculegion, oinkologne are already handled in the dex
  28. )
  29. @dataclass
  30. class Form:
  31. name: str
  32. traits: list[str]
  33. types: list[str]
  34. color: str
  35. @dataclass
  36. class Pokemon:
  37. num: int
  38. species: str
  39. forms: list[Form]
  40. async def download_pokedex() -> dict:
  41. async with ClientSession() as session:
  42. async with session.get("https://play.pokemonshowdown.com/data/pokedex.js") as res:
  43. res.raise_for_status()
  44. text = await res.text("utf-8")
  45. # this is not json of course, but it's close
  46. # start by taking out the ; and definition
  47. cleaned = text.replace("exports.BattlePokedex = ", "").strip(";")
  48. # then convert the keys to strings
  49. converted = re.sub(JS_TO_JSON, lambda m: f'"{m.group(1)}":', cleaned)
  50. # and fix Type: Null, Farfetch'd, Sirfetch'd
  51. fixed = converted.replace(
  52. '""Type": Null"', '"Type: Null"').replace("\u2019", "'")
  53. # then, parse it
  54. return json.loads(fixed)
  55. START_IN_BASE_FORM = (
  56. "Castform", # castform can't start battle in weather forms
  57. "Cherrim", # castform can't start battle in sunshine form
  58. "Greninja", # greninja can't start battle in Ash form
  59. "Aegislash", # aegislash can't start battle in blade form
  60. "Wishiwashi", # wishiwashi can't start battle in schooling form
  61. "Mimikyu", # mimikyu can't start battle in busted form
  62. "Cramorant", # cramorant can't start battle in gulping/gorging form
  63. "Eiscue", # eiscue can't start battle in noice form
  64. "Morpeko", # morpeko can't start battle in hangry form
  65. "Palafin", # palafin can only start in zero form
  66. "Gimmighoul", # gimmighoul roaming is only in PGO
  67. )
  68. def get_traits(species: str, form_info: dict) -> list[str]:
  69. traits = []
  70. if len(form_info.get("evos", [])) > 0:
  71. traits.append("nfe")
  72. kind = form_info["formeKind"].lower()
  73. if kind in ("mega", "mega-x", "mega-y", "primal"):
  74. traits.extend(("mega", "nostart"))
  75. if kind in ("gmax", "eternamax", "rapid-strike-gmax"):
  76. traits.extend(("gmax", "nostart"))
  77. if kind in ("alola", "galar", "hisui", "paldea"):
  78. traits.extend(("regional", kind))
  79. if species in START_IN_BASE_FORM and kind != "base":
  80. traits.append("nostart")
  81. # special cases
  82. if species == "Necrozma" and kind == "ultra":
  83. # necrozma can't start battle in ultra form
  84. traits.append("nostart")
  85. if species == "Tauros" and "paldea" in kind:
  86. # paldean tauros has dumb names
  87. traits.extend(("regional", "paldea"))
  88. if species == "Minior" and kind != "meteor":
  89. # minior can only start the battle in meteor form
  90. traits.append("nostart")
  91. if species == "Darmanitan" and "zen" in kind:
  92. # darmanitan cannot start in zen form
  93. traits.append("nostart")
  94. if "galar" in kind:
  95. # also there's a galar-zen form to handle
  96. traits.extend(("regional", "galar"))
  97. return sorted(set(traits))
  98. def clean_dex(raw: dict) -> dict[int, Pokemon]:
  99. regrouped = defaultdict(list)
  100. for key, entry in raw.items():
  101. isNonstandard = entry.get("isNonstandard", None)
  102. baseSpecies = entry.get("baseSpecies", None)
  103. forme = entry.get("forme", None)
  104. if isNonstandard not in (None, "Past", "Unobtainable"):
  105. continue # remove CAP etc.
  106. if baseSpecies in ("Pikachu", "Pichu") and forme is not None:
  107. continue # remove pikachu spam + spiky ear pichu
  108. if forme is not None and "Totem" in forme:
  109. continue # remove totem pokemon
  110. if baseSpecies == "Toxtricity" and forme == "Low-Key-Gmax":
  111. continue # remove low-key-gmax since it is sort of a duplicate
  112. if baseSpecies == "Greninja" and forme == "Bond":
  113. continue # remove bond greninja since it is basically a visual duplicate
  114. if baseSpecies == "Ogerpon" and "Tera" in forme:
  115. continue # seems to have no visual change as of 11/25
  116. num = entry["num"]
  117. # non-cosmetic forms get separate entries automatically
  118. # but keeping the separate unown forms would be ridiculous
  119. if key != "unown" and len(cosmetic := entry.get("cosmeticFormes", [])) > 0:
  120. cosmetic.append(f'{entry["name"]}-{entry["baseForme"]}')
  121. if key == "alcremie":
  122. # oh god this thing
  123. cosmetic = [
  124. f"{cf}-{sweet}"
  125. for cf in cosmetic
  126. for sweet in ALCREMIE_SWEETS
  127. ]
  128. regrouped[num].extend({
  129. **entry,
  130. "forme": cf.replace(" ", "-"),
  131. "formeKind": "cosmetic",
  132. } for cf in cosmetic)
  133. elif key in GENDER_DIFFS:
  134. regrouped[num].append({
  135. **entry,
  136. "forme": f'{entry["name"]}-M',
  137. "formeKind": "cosmetic",
  138. })
  139. regrouped[num].append({
  140. **entry,
  141. "forme": f'{entry["name"]}-F',
  142. "formeKind": "cosmetic",
  143. })
  144. else:
  145. regrouped[num].append({
  146. **entry,
  147. "forme": entry["name"],
  148. "formeKind": entry.get("forme", "base").lower(),
  149. })
  150. return {
  151. i: Pokemon(
  152. num=i,
  153. species=(
  154. # doubles as an assertion that forms is not empty
  155. species := (forms := regrouped[i])[0].get("baseSpecies", forms[0]["name"])
  156. ),
  157. forms=[
  158. Form(
  159. name=f.get("forme", f["name"]),
  160. traits=get_traits(species, f),
  161. types=f["types"],
  162. color=f["color"],
  163. ) for f in forms
  164. ]
  165. ) for i in range(1, max(regrouped.keys()) + 1)
  166. }
  167. async def load_pokedex(dex_file: Path, force_dex: bool) -> dict:
  168. if dex_file.is_file() and not force_dex:
  169. with open(dex_file) as infile:
  170. loaded = json.load(infile)
  171. dex = {
  172. int(num): Pokemon(
  173. num=entry["num"],
  174. species=entry["species"],
  175. forms=[Form(**f) for f in entry["forms"]],
  176. ) for num, entry in loaded.items()
  177. }
  178. else:
  179. # first download the pokedex
  180. raw_dex = await download_pokedex()
  181. # clean and reorganize it
  182. dex = clean_dex(raw_dex)
  183. # output dex for auditing and reloading
  184. with open(dex_file, "w") as out:
  185. json.dump({
  186. str(i): asdict(pkmn)
  187. for i, pkmn in dex.items()
  188. }, out, indent=2)
  189. return dex
  190. SHOWDOWN_STRIP_FORM = (
  191. "burmy-plant",
  192. "shellos-west", "gastrodon-west",
  193. "deerling-spring", "sawsbuck-spring",
  194. "flabébé-red", "floette-red", "florges-red",
  195. "minior-red",
  196. "vivillon-meadow",
  197. "furfrou-natural",
  198. "alcremie-vanilla-cream-strawberry",
  199. "tatsugiri-curly",
  200. )
  201. # . for mime jr + mr mime + mr rime
  202. # ' for farfetch'd + sirfetch'd, also oricorio-pa'u
  203. # % for zygarde 10%
  204. # : for type: null
  205. # space mostly for tapus and paradox mons, but also some others
  206. SHOWDOWN_REMOVE_SYMBOLS = ".'%: "
  207. SHOWDOWN_REPLACEMENTS = [
  208. ("mega-", "mega"), # charizard, mewtwo
  209. ("paldea-", "paldea"), # tauros
  210. ("nidoran-m", "nidoranm"),
  211. ("nidoran-f", "nidoranf"),
  212. ("ho-oh", "hooh"),
  213. ("porygon-z", "porygonz"),
  214. ("-striped", "striped"), # basculin special cases
  215. ("-galar-zen", "-galarzen"), # darmanitan special cases
  216. ("é", "e"), # flabebe
  217. ("vivillon-icy-snow", "vivillon-icysnow"),
  218. ("vivillon-high-plains", "vivillon-highplains"),
  219. ("furfrou-la-reine", "furfrou-lareine"),
  220. ("oricorio-pom-pom", "oricorio-pompom"),
  221. ("mo-o", "moo"), # jangmo-o, hakamo-o, kommo-o
  222. ("dusk-mane", "duskmane"), # necrozma
  223. ("dawn-wings", "dawnwings"), # necrozma
  224. ("low-key", "lowkey"), # toxtricity
  225. ("-swirl", "swirl"), # alcremie
  226. ("-cream", "cream"), # alcremie
  227. ("-strawberry", ""), # alcremie
  228. ("rapid-strike", "rapidstrike"), # Urshifu
  229. ("rapidstrike-gmax", "rapidstrikegmax"), # Urshifu
  230. # dudunsparce (note - no visual diff on showdown as of 3/17)
  231. ("three-segment", "threesegment"),
  232. ("wo-chien", "wochien"),
  233. ("chien-pao", "chienpao"),
  234. ("ting-lu", "tinglu"),
  235. ("chi-yu", "chiyu"),
  236. ]
  237. def get_showdown_urls(pkmn: Pokemon, form: Form) -> list[tuple[str, str]]:
  238. name = form.name.lower()
  239. if name in SHOWDOWN_STRIP_FORM:
  240. name = name.split("-", 1)[0]
  241. for c in SHOWDOWN_REMOVE_SYMBOLS:
  242. name = name.replace(c, "")
  243. for pat, ins in SHOWDOWN_REPLACEMENTS:
  244. name = name.replace(pat, ins)
  245. name = re.sub(r"-m$", "", name) # gender diff forms
  246. # lol they just reuse the male back sprite for wobbuffet
  247. back_name = name if name != "wobbuffet-f" else "wobbuffet"
  248. return [
  249. (f"https://play.pokemonshowdown.com/sprites/ani/{name}.gif", "gif"),
  250. (f"https://play.pokemonshowdown.com/sprites/ani-back/{back_name}.gif", "gif"),
  251. (f"https://play.pokemonshowdown.com/sprites/gen5/{name}.png", "png"),
  252. (f"https://play.pokemonshowdown.com/sprites/gen5-back/{back_name}.png", "png"),
  253. ]
  254. SEREBII_USE_SPECIES = (
  255. "Wobbuffet-M",
  256. "Shellos-West", "Gastrodon-West",
  257. "Hippopotas-M", "Hippowdon-M",
  258. "Unfezant-M",
  259. "Deerling-Spring", "Sawsbuck-Spring",
  260. "Frillish-M", "Jellicent-M",
  261. "Pyroar-M",
  262. "Furfrou-Natural",
  263. "Minior-Meteor",
  264. "Tatsugiri-Curly",
  265. )
  266. SEREBII_FORM_FIRST_LETTER = (
  267. "Deoxys-Attack", "Deoxys-Defense", "Deoxys-Speed",
  268. "Burmy-Plant", "Burmy-Sandy", "Burmy-Trash",
  269. "Cherrim-Sunshine",
  270. "Shellos-East", "Gastrodon-East",
  271. "Dialga-Origin", "Palkia-Origin", "Giratina-Origin",
  272. "Shaymin-Sky",
  273. "Unfezant-F",
  274. "Basculin-Blue-Striped", "Basculin-White-Striped",
  275. "Deerling-Summer", "Deerling-Autumn", "Deerling-Winter",
  276. "Sawsbuck-Summer", "Sawsbuck-Autumn", "Sawsbuck-Winter",
  277. "Frillish-F", "Jellicent-F",
  278. "Kyurem-Black", "Kyurem-White", "Keldeo-Resolute",
  279. "Greninja-Ash",
  280. "Pyroar-F",
  281. "Flabébé-Blue", "Flabébé-Orange", "Flabébé-White", "Flabébé-Yellow", "Flabébé-Red",
  282. "Floette-Blue", "Floette-Orange", "Floette-White", "Floette-Yellow", "Floette-Red",
  283. "Florges-Blue", "Florges-Orange", "Florges-White", "Florges-Yellow", "Florges-Red",
  284. "Meowstic-F",
  285. "Aegislash-Blade",
  286. "Hoopa-Unbound",
  287. "Lycanroc-Midnight", "Lycanroc-Dusk",
  288. "Wishiwashi-School",
  289. "Minior-Blue", "Minior-Green", "Minior-Indigo", "Minior-Orange", "Minior-Red", "Minior-Violet", "Minior-Yellow",
  290. "Mimikyu-Busted",
  291. "Magearna-Original",
  292. "Toxtricity-Low-Key",
  293. "Eiscue-Noice",
  294. "Indeedee-F",
  295. "Morpeko-Hangry",
  296. "Zacian-Crowned", "Zamazenta-Crowned",
  297. "Eternatus-Eternamax",
  298. "Zarude-Dada",
  299. "Calyrex-Ice", "Calyrex-Shadow",
  300. "Basculegion-F",
  301. "Ursaluna-Bloodmoon",
  302. "Oinkologne-F",
  303. "Maushold-Four",
  304. "Palafin-Hero",
  305. "Tatsugiri-Droopy", "Tatsugiri-Stretchy",
  306. "Dudunsparce-Three-Segment",
  307. "Gimmighoul-Roaming",
  308. "Ogerpon-Wellspring",
  309. "Ogerpon-Hearthflame",
  310. "Ogerpon-Cornerstone",
  311. )
  312. SEREBII_SPECIAL = {
  313. "Castform-Rainy": "r",
  314. "Castform-Snowy": "i",
  315. "Castform-Sunny": "s",
  316. # wormadam plant is default form
  317. "Wormadam-Sandy": "c",
  318. "Wormadam-Trash": "t",
  319. "Tauros-Paldea-Blaze": "b",
  320. "Tauros-Paldea-Aqua": "a",
  321. "Rotom-Heat": "h",
  322. "Rotom-Wash": "w",
  323. "Rotom-Frost": "f",
  324. "Rotom-Fan": "s",
  325. "Rotom-Mow": "m",
  326. "Darmanitan-Zen": "z",
  327. "Darmanitan-Galar-Zen": "gz",
  328. "Meloetta-Pirouette": "s",
  329. "Genesect-Douse": "w",
  330. "Genesect-Shock": "e",
  331. "Genesect-Burn": "f",
  332. "Genesect-Chill": "i",
  333. "Vivillon-Archipelago": "a",
  334. "Vivillon-Continental": "c",
  335. "Vivillon-Elegant": "e",
  336. "Vivillon-Fancy": "f",
  337. "Vivillon-Garden": "g",
  338. "Vivillon-High-Plains": "h",
  339. "Vivillon-Icy-Snow": "i",
  340. "Vivillon-Jungle": "j",
  341. "Vivillon-Marine": "ma",
  342. "Vivillon-Meadow": "m",
  343. "Vivillon-Modern": "mo",
  344. "Vivillon-Monsoon": "mon",
  345. "Vivillon-Ocean": "o",
  346. "Vivillon-Pokeball": "pb",
  347. "Vivillon-Polar": "p",
  348. "Vivillon-River": "r",
  349. "Vivillon-Sandstorm": "s",
  350. "Vivillon-Savanna": "sa",
  351. "Vivillon-Sun": "su",
  352. "Vivillon-Tundra": "t",
  353. "Furfrou-Dandy": "da",
  354. "Furfrou-Debutante": "de",
  355. "Furfrou-Diamond": "d",
  356. "Furfrou-Heart": "h",
  357. "Furfrou-Kabuki": "k",
  358. "Furfrou-La-Reine": "l",
  359. "Furfrou-Matron": "m",
  360. "Furfrou-Pharaoh": "p",
  361. "Furfrou-Star": "s",
  362. "Zygarde-10%": "10",
  363. "Zygarde-Complete": "c",
  364. "Pumpkaboo-Small": "s", # kinda dumb but w/e
  365. "Pumpkaboo-Large": "l",
  366. "Pumpkaboo-Super": "h",
  367. "Gourgeist-Small": "s",
  368. "Gourgeist-Large": "l",
  369. "Gourgeist-Super": "h",
  370. "Oricorio-Pom-Pom": "p",
  371. "Oricorio-Pa'u": "pau",
  372. "Oricorio-Sensu": "s",
  373. "Necrozma-Dusk-Mane": "dm",
  374. "Necrozma-Dawn-Wings": "dw",
  375. "Necrozma-Ultra": "m",
  376. "Cramorant-Gulping": "gu",
  377. "Cramorant-Gorging": "go",
  378. "Sinistea-Antique": "f",
  379. "Polteageist-Antique": "f",
  380. "Urshifu-Rapid-Strike": "r",
  381. "Urshifu-Rapid-Strike-Gmax": "rgi",
  382. }
  383. SEREBII_IGNORE_MISSING = (
  384. "Wobbuffet-F", "Hippopotas-F", "Hippowdon-F", "Floette-Eternal",
  385. "Squawkabilly-Blue", "Squawkabilly-Yellow", "Squawkabilly-White",
  386. "Poltchageist-Artisan", "Sinistcha-Masterpiece",
  387. )
  388. def get_serebii_url(pkmn: Pokemon, form: Form) -> str | None:
  389. if form.name == pkmn.species or form.name in SEREBII_USE_SPECIES:
  390. return f"https://www.serebii.net/pokemon/art/{pkmn.num:03d}.png"
  391. if form.name in SEREBII_FORM_FIRST_LETTER:
  392. letter = form.name.split("-", 1)[1][0].lower()
  393. return f"https://www.serebii.net/pokemon/art/{pkmn.num:03d}-{letter}.png"
  394. if form.name in SEREBII_SPECIAL:
  395. return f"https://www.serebii.net/pokemon/art/{pkmn.num:03d}-{SEREBII_SPECIAL[form.name]}.png"
  396. if "gmax" in form.traits:
  397. return f"https://www.serebii.net/pokemon/art/{pkmn.num:03d}-gi.png"
  398. if "mega" in form.traits:
  399. if "Mega-X" in form.name:
  400. return f"https://www.serebii.net/pokemon/art/{pkmn.num:03d}-mx.png"
  401. elif "Mega-Y" in form.name:
  402. return f"https://www.serebii.net/pokemon/art/{pkmn.num:03d}-my.png"
  403. else:
  404. return f"https://www.serebii.net/pokemon/art/{pkmn.num:03d}-m.png"
  405. if "alola" in form.traits:
  406. return f"https://www.serebii.net/pokemon/art/{pkmn.num:03d}-a.png"
  407. if "galar" in form.traits:
  408. return f"https://www.serebii.net/pokemon/art/{pkmn.num:03d}-g.png"
  409. if "hisui" in form.traits:
  410. return f"https://www.serebii.net/pokemon/art/{pkmn.num:03d}-h.png"
  411. if "paldea" in form.traits:
  412. return f"https://www.serebii.net/pokemon/art/{pkmn.num:03d}-p.png"
  413. if pkmn.num in (493, 773): # arceus and silvally
  414. type_name = form.name.split('-')[1].lower()
  415. return f"https://www.serebii.net/pokemon/art/{pkmn.num}-{type_name}.png"
  416. if pkmn.num == 869: # alcremie
  417. _, cream_name, cream_kind, candy_name = form.name.lower().split("-")
  418. image_name = "869-"
  419. if cream_name == "matcha":
  420. image_name += "mac"
  421. elif cream_name == "mint":
  422. image_name += "mic"
  423. elif cream_name != "vanilla":
  424. image_name += cream_name[0] + cream_kind[0]
  425. if candy_name != "strawberry":
  426. if candy_name == "love" and cream_name == "mint":
  427. # for some reason this name breaks the pattern
  428. image_name += "heart"
  429. else:
  430. image_name += candy_name
  431. image_name = image_name.strip("-")
  432. return f"https://www.serebii.net/swordshield/pokemon/{image_name}.png"
  433. if "-Therian" in form.name:
  434. return f"https://www.serebii.net/pokemon/art/{pkmn.num:03d}-s.png"
  435. if form.name not in SEREBII_IGNORE_MISSING:
  436. print(f"No Serebii URL known for {form.name}")
  437. async def download(session: ClientSession, url: str, filename: Path, staging: bool = False) -> tuple[str, Exception | bool]:
  438. if filename.is_file() or staging:
  439. return url, False
  440. try:
  441. async with session.get(url) as res:
  442. res.raise_for_status()
  443. with open(filename, "wb") as out:
  444. out.write(await res.read())
  445. except Exception as ex:
  446. return url, ex
  447. return url, True
  448. async def download_all_for_pokemon(pkmn: Pokemon, image_dir: Path, staging: bool = False) -> dict[str, dict[str, Exception | bool]]:
  449. results = defaultdict(dict)
  450. async with ClientSession() as session:
  451. for form in pkmn.forms:
  452. urls = []
  453. urls += get_showdown_urls(pkmn, form)
  454. urls.append((get_serebii_url(pkmn, form), "png"))
  455. # TODO more sources
  456. results[form.name].update(await asyncio.gather(*[
  457. download(session, url, image_dir.joinpath(
  458. f"{form.name}-{i}.{ext}"), staging)
  459. for i, (url, ext) in enumerate(urls) if url is not None
  460. ]))
  461. return results
  462. async def download_all(image_dir: Path, pkmn: list[Pokemon], staging: bool = False) -> dict[str, dict[str, Exception | bool]]:
  463. image_dir.mkdir(parents=True, exist_ok=True)
  464. log = {}
  465. for p in pkmn:
  466. log.update(await download_all_for_pokemon(p, image_dir, staging))
  467. return log
  468. KNOWN_MISSING = [
  469. "https://play.pokemonshowdown.com/sprites/ani/venusaur-gmax.gif",
  470. "https://play.pokemonshowdown.com/sprites/ani-back/venusaur-gmax.gif",
  471. "https://play.pokemonshowdown.com/sprites/ani/blastoise-gmax.gif",
  472. "https://play.pokemonshowdown.com/sprites/ani-back/blastoise-gmax.gif",
  473. "https://play.pokemonshowdown.com/sprites/ani/growlithe-hisui.gif",
  474. "https://play.pokemonshowdown.com/sprites/ani-back/growlithe-hisui.gif",
  475. "https://play.pokemonshowdown.com/sprites/ani/arcanine-hisui.gif",
  476. "https://play.pokemonshowdown.com/sprites/ani-back/arcanine-hisui.gif",
  477. "https://play.pokemonshowdown.com/sprites/ani/voltorb-hisui.gif",
  478. "https://play.pokemonshowdown.com/sprites/ani-back/voltorb-hisui.gif",
  479. "https://play.pokemonshowdown.com/sprites/ani/electrode-hisui.gif",
  480. "https://play.pokemonshowdown.com/sprites/ani-back/electrode-hisui.gif",
  481. "https://play.pokemonshowdown.com/sprites/ani/tauros-paldeacombat.gif",
  482. "https://play.pokemonshowdown.com/sprites/ani-back/tauros-paldeacombat.gif",
  483. "https://play.pokemonshowdown.com/sprites/ani/tauros-paldeablaze.gif",
  484. "https://play.pokemonshowdown.com/sprites/ani-back/tauros-paldeablaze.gif",
  485. "https://play.pokemonshowdown.com/sprites/ani/tauros-paldeaaqua.gif",
  486. "https://play.pokemonshowdown.com/sprites/ani-back/tauros-paldeaaqua.gif",
  487. "https://play.pokemonshowdown.com/sprites/ani/wooper-paldea.gif",
  488. "https://play.pokemonshowdown.com/sprites/ani-back/wooper-paldea.gif",
  489. "https://play.pokemonshowdown.com/sprites/ani/qwilfish-hisui.gif",
  490. "https://play.pokemonshowdown.com/sprites/ani-back/qwilfish-hisui.gif",
  491. "https://play.pokemonshowdown.com/sprites/ani/sneasel-hisui.gif",
  492. "https://play.pokemonshowdown.com/sprites/ani-back/sneasel-hisui.gif",
  493. "https://play.pokemonshowdown.com/sprites/ani/dialga-origin.gif",
  494. "https://play.pokemonshowdown.com/sprites/ani-back/dialga-origin.gif",
  495. "https://play.pokemonshowdown.com/sprites/ani/palkia-origin.gif",
  496. "https://play.pokemonshowdown.com/sprites/ani-back/palkia-origin.gif",
  497. "https://play.pokemonshowdown.com/sprites/ani/lilligant-hisui.gif",
  498. "https://play.pokemonshowdown.com/sprites/ani-back/lilligant-hisui.gif",
  499. "https://play.pokemonshowdown.com/sprites/ani/basculin-whitestriped.gif",
  500. "https://play.pokemonshowdown.com/sprites/ani-back/basculin-whitestriped.gif",
  501. "https://play.pokemonshowdown.com/sprites/ani/braviary-hisui.gif",
  502. "https://play.pokemonshowdown.com/sprites/ani-back/braviary-hisui.gif",
  503. "https://play.pokemonshowdown.com/sprites/ani/sliggoo-hisui.gif",
  504. "https://play.pokemonshowdown.com/sprites/ani-back/sliggoo-hisui.gif",
  505. "https://play.pokemonshowdown.com/sprites/ani/goodra-hisui.gif",
  506. "https://play.pokemonshowdown.com/sprites/ani-back/goodra-hisui.gif",
  507. "https://play.pokemonshowdown.com/sprites/ani/avalugg-hisui.gif",
  508. "https://play.pokemonshowdown.com/sprites/ani-back/avalugg-hisui.gif",
  509. "https://play.pokemonshowdown.com/sprites/ani/rillaboom-gmax.gif",
  510. "https://play.pokemonshowdown.com/sprites/ani-back/rillaboom-gmax.gif",
  511. "https://play.pokemonshowdown.com/sprites/ani/cinderace-gmax.gif",
  512. "https://play.pokemonshowdown.com/sprites/ani-back/cinderace-gmax.gif",
  513. "https://play.pokemonshowdown.com/sprites/ani-back/inteleon-gmax.gif",
  514. "https://play.pokemonshowdown.com/sprites/ani/urshifu-gmax.gif",
  515. "https://play.pokemonshowdown.com/sprites/ani-back/urshifu-gmax.gif",
  516. "https://play.pokemonshowdown.com/sprites/ani/urshifu-rapidstrikegmax.gif",
  517. "https://play.pokemonshowdown.com/sprites/ani-back/urshifu-rapidstrikegmax.gif",
  518. "https://play.pokemonshowdown.com/sprites/ani/wyrdeer.gif",
  519. "https://play.pokemonshowdown.com/sprites/ani-back/wyrdeer.gif",
  520. "https://play.pokemonshowdown.com/sprites/ani/kleavor.gif",
  521. "https://play.pokemonshowdown.com/sprites/ani-back/kleavor.gif",
  522. "https://play.pokemonshowdown.com/sprites/ani/ursaluna.gif",
  523. "https://play.pokemonshowdown.com/sprites/ani-back/ursaluna.gif",
  524. "https://play.pokemonshowdown.com/sprites/ani/ursaluna-bloodmoon.gif",
  525. "https://play.pokemonshowdown.com/sprites/ani-back/ursaluna-bloodmoon.gif",
  526. "https://play.pokemonshowdown.com/sprites/ani/sneasler.gif",
  527. "https://play.pokemonshowdown.com/sprites/ani-back/sneasler.gif",
  528. "https://play.pokemonshowdown.com/sprites/ani/overqwil.gif",
  529. "https://play.pokemonshowdown.com/sprites/ani-back/overqwil.gif",
  530. "https://play.pokemonshowdown.com/sprites/ani/enamorus.gif",
  531. "https://play.pokemonshowdown.com/sprites/ani-back/enamorus.gif",
  532. "https://play.pokemonshowdown.com/sprites/ani/enamorus-therian.gif",
  533. "https://play.pokemonshowdown.com/sprites/ani-back/enamorus-therian.gif",
  534. "https://play.pokemonshowdown.com/sprites/ani/pawmi.gif",
  535. "https://play.pokemonshowdown.com/sprites/ani-back/pawmi.gif",
  536. "https://play.pokemonshowdown.com/sprites/ani/pawmo.gif",
  537. "https://play.pokemonshowdown.com/sprites/ani-back/pawmo.gif",
  538. "https://play.pokemonshowdown.com/sprites/ani/pawmot.gif",
  539. "https://play.pokemonshowdown.com/sprites/ani-back/pawmot.gif",
  540. "https://play.pokemonshowdown.com/sprites/ani/tandemaus.gif",
  541. "https://play.pokemonshowdown.com/sprites/ani-back/tandemaus.gif",
  542. "https://play.pokemonshowdown.com/sprites/ani/maushold.gif",
  543. "https://play.pokemonshowdown.com/sprites/ani-back/maushold.gif",
  544. "https://play.pokemonshowdown.com/sprites/ani/maushold-four.gif",
  545. "https://play.pokemonshowdown.com/sprites/ani-back/maushold-four.gif",
  546. "https://play.pokemonshowdown.com/sprites/ani/fidough.gif",
  547. "https://play.pokemonshowdown.com/sprites/ani-back/fidough.gif",
  548. "https://play.pokemonshowdown.com/sprites/ani/dachsbun.gif",
  549. "https://play.pokemonshowdown.com/sprites/ani-back/dachsbun.gif",
  550. "https://play.pokemonshowdown.com/sprites/ani/squawkabilly.gif",
  551. "https://play.pokemonshowdown.com/sprites/ani-back/squawkabilly.gif",
  552. "https://play.pokemonshowdown.com/sprites/ani/squawkabilly-blue.gif",
  553. "https://play.pokemonshowdown.com/sprites/ani-back/squawkabilly-blue.gif",
  554. "https://play.pokemonshowdown.com/sprites/ani/squawkabilly-yellow.gif",
  555. "https://play.pokemonshowdown.com/sprites/ani-back/squawkabilly-yellow.gif",
  556. "https://play.pokemonshowdown.com/sprites/ani/squawkabilly-white.gif",
  557. "https://play.pokemonshowdown.com/sprites/ani-back/squawkabilly-white.gif",
  558. "https://play.pokemonshowdown.com/sprites/ani/nacli.gif",
  559. "https://play.pokemonshowdown.com/sprites/ani-back/nacli.gif",
  560. "https://play.pokemonshowdown.com/sprites/ani/naclstack.gif",
  561. "https://play.pokemonshowdown.com/sprites/ani-back/naclstack.gif",
  562. "https://play.pokemonshowdown.com/sprites/ani/garganacl.gif",
  563. "https://play.pokemonshowdown.com/sprites/ani-back/garganacl.gif",
  564. "https://play.pokemonshowdown.com/sprites/ani/charcadet.gif",
  565. "https://play.pokemonshowdown.com/sprites/ani-back/charcadet.gif",
  566. "https://play.pokemonshowdown.com/sprites/ani/armarouge.gif",
  567. "https://play.pokemonshowdown.com/sprites/ani-back/armarouge.gif",
  568. "https://play.pokemonshowdown.com/sprites/ani/ceruledge.gif",
  569. "https://play.pokemonshowdown.com/sprites/ani-back/ceruledge.gif",
  570. "https://play.pokemonshowdown.com/sprites/ani/tadbulb.gif",
  571. "https://play.pokemonshowdown.com/sprites/ani-back/tadbulb.gif",
  572. "https://play.pokemonshowdown.com/sprites/ani/bellibolt.gif",
  573. "https://play.pokemonshowdown.com/sprites/ani-back/bellibolt.gif",
  574. "https://play.pokemonshowdown.com/sprites/ani/wattrel.gif",
  575. "https://play.pokemonshowdown.com/sprites/ani-back/wattrel.gif",
  576. "https://play.pokemonshowdown.com/sprites/ani/kilowattrel.gif",
  577. "https://play.pokemonshowdown.com/sprites/ani-back/kilowattrel.gif",
  578. "https://play.pokemonshowdown.com/sprites/ani/maschiff.gif",
  579. "https://play.pokemonshowdown.com/sprites/ani-back/maschiff.gif",
  580. "https://play.pokemonshowdown.com/sprites/ani/mabosstiff.gif",
  581. "https://play.pokemonshowdown.com/sprites/ani-back/mabosstiff.gif",
  582. "https://play.pokemonshowdown.com/sprites/ani/shroodle.gif",
  583. "https://play.pokemonshowdown.com/sprites/ani-back/shroodle.gif",
  584. "https://play.pokemonshowdown.com/sprites/ani/grafaiai.gif",
  585. "https://play.pokemonshowdown.com/sprites/ani-back/grafaiai.gif",
  586. "https://play.pokemonshowdown.com/sprites/ani/bramblin.gif",
  587. "https://play.pokemonshowdown.com/sprites/ani-back/bramblin.gif",
  588. "https://play.pokemonshowdown.com/sprites/ani/brambleghast.gif",
  589. "https://play.pokemonshowdown.com/sprites/ani-back/brambleghast.gif",
  590. "https://play.pokemonshowdown.com/sprites/ani/toedscool.gif",
  591. "https://play.pokemonshowdown.com/sprites/ani-back/toedscool.gif",
  592. "https://play.pokemonshowdown.com/sprites/ani/toedscruel.gif",
  593. "https://play.pokemonshowdown.com/sprites/ani-back/toedscruel.gif",
  594. "https://play.pokemonshowdown.com/sprites/ani/klawf.gif",
  595. "https://play.pokemonshowdown.com/sprites/ani-back/klawf.gif",
  596. "https://play.pokemonshowdown.com/sprites/ani/tinkatink.gif",
  597. "https://play.pokemonshowdown.com/sprites/ani-back/tinkatink.gif",
  598. "https://play.pokemonshowdown.com/sprites/ani/tinkatuff.gif",
  599. "https://play.pokemonshowdown.com/sprites/ani-back/tinkatuff.gif",
  600. "https://play.pokemonshowdown.com/sprites/ani/tinkaton.gif",
  601. "https://play.pokemonshowdown.com/sprites/ani-back/tinkaton.gif",
  602. "https://play.pokemonshowdown.com/sprites/ani/bombirdier.gif",
  603. "https://play.pokemonshowdown.com/sprites/ani-back/bombirdier.gif",
  604. "https://play.pokemonshowdown.com/sprites/ani/varoom.gif",
  605. "https://play.pokemonshowdown.com/sprites/ani-back/varoom.gif",
  606. "https://play.pokemonshowdown.com/sprites/ani/revavroom.gif",
  607. "https://play.pokemonshowdown.com/sprites/ani-back/revavroom.gif",
  608. "https://play.pokemonshowdown.com/sprites/ani/cyclizar.gif",
  609. "https://play.pokemonshowdown.com/sprites/ani-back/cyclizar.gif",
  610. "https://play.pokemonshowdown.com/sprites/ani/orthworm.gif",
  611. "https://play.pokemonshowdown.com/sprites/ani-back/orthworm.gif",
  612. "https://play.pokemonshowdown.com/sprites/ani/glimmet.gif",
  613. "https://play.pokemonshowdown.com/sprites/ani-back/glimmet.gif",
  614. "https://play.pokemonshowdown.com/sprites/ani/glimmora.gif",
  615. "https://play.pokemonshowdown.com/sprites/ani-back/glimmora.gif",
  616. "https://play.pokemonshowdown.com/sprites/ani/flamigo.gif",
  617. "https://play.pokemonshowdown.com/sprites/ani-back/flamigo.gif",
  618. "https://play.pokemonshowdown.com/sprites/ani/cetoddle.gif",
  619. "https://play.pokemonshowdown.com/sprites/ani-back/cetoddle.gif",
  620. "https://play.pokemonshowdown.com/sprites/ani/cetitan.gif",
  621. "https://play.pokemonshowdown.com/sprites/ani-back/cetitan.gif",
  622. "https://play.pokemonshowdown.com/sprites/ani/tatsugiri-droopy.gif",
  623. "https://play.pokemonshowdown.com/sprites/ani-back/tatsugiri-droopy.gif",
  624. "https://play.pokemonshowdown.com/sprites/ani/tatsugiri-stretchy.gif",
  625. "https://play.pokemonshowdown.com/sprites/ani-back/tatsugiri-stretchy.gif",
  626. "https://play.pokemonshowdown.com/sprites/ani/tatsugiri.gif",
  627. "https://play.pokemonshowdown.com/sprites/ani-back/tatsugiri.gif",
  628. "https://play.pokemonshowdown.com/sprites/ani/annihilape.gif",
  629. "https://play.pokemonshowdown.com/sprites/ani-back/annihilape.gif",
  630. "https://play.pokemonshowdown.com/sprites/ani/clodsire.gif",
  631. "https://play.pokemonshowdown.com/sprites/ani-back/clodsire.gif",
  632. "https://play.pokemonshowdown.com/sprites/ani/kingambit.gif",
  633. "https://play.pokemonshowdown.com/sprites/ani-back/kingambit.gif",
  634. "https://play.pokemonshowdown.com/sprites/ani/greattusk.gif",
  635. "https://play.pokemonshowdown.com/sprites/ani-back/greattusk.gif",
  636. "https://play.pokemonshowdown.com/sprites/ani/screamtail.gif",
  637. "https://play.pokemonshowdown.com/sprites/ani-back/screamtail.gif",
  638. "https://play.pokemonshowdown.com/sprites/ani/brutebonnet.gif",
  639. "https://play.pokemonshowdown.com/sprites/ani-back/brutebonnet.gif",
  640. "https://play.pokemonshowdown.com/sprites/ani/fluttermane.gif",
  641. "https://play.pokemonshowdown.com/sprites/ani-back/fluttermane.gif",
  642. "https://play.pokemonshowdown.com/sprites/ani/slitherwing.gif",
  643. "https://play.pokemonshowdown.com/sprites/ani-back/slitherwing.gif",
  644. "https://play.pokemonshowdown.com/sprites/ani/sandyshocks.gif",
  645. "https://play.pokemonshowdown.com/sprites/ani-back/sandyshocks.gif",
  646. "https://play.pokemonshowdown.com/sprites/ani/irontreads.gif",
  647. "https://play.pokemonshowdown.com/sprites/ani-back/irontreads.gif",
  648. "https://play.pokemonshowdown.com/sprites/ani/ironbundle.gif",
  649. "https://play.pokemonshowdown.com/sprites/ani-back/ironbundle.gif",
  650. "https://play.pokemonshowdown.com/sprites/ani/ironhands.gif",
  651. "https://play.pokemonshowdown.com/sprites/ani-back/ironhands.gif",
  652. "https://play.pokemonshowdown.com/sprites/ani/ironjugulis.gif",
  653. "https://play.pokemonshowdown.com/sprites/ani-back/ironjugulis.gif",
  654. "https://play.pokemonshowdown.com/sprites/ani/ironmoth.gif",
  655. "https://play.pokemonshowdown.com/sprites/ani-back/ironmoth.gif",
  656. "https://play.pokemonshowdown.com/sprites/ani/ironthorns.gif",
  657. "https://play.pokemonshowdown.com/sprites/ani-back/ironthorns.gif",
  658. "https://play.pokemonshowdown.com/sprites/ani/frigibax.gif",
  659. "https://play.pokemonshowdown.com/sprites/ani-back/frigibax.gif",
  660. "https://play.pokemonshowdown.com/sprites/ani/arctibax.gif",
  661. "https://play.pokemonshowdown.com/sprites/ani-back/arctibax.gif",
  662. "https://play.pokemonshowdown.com/sprites/ani/baxcalibur.gif",
  663. "https://play.pokemonshowdown.com/sprites/ani-back/baxcalibur.gif",
  664. "https://play.pokemonshowdown.com/sprites/ani/gimmighoul.gif",
  665. "https://play.pokemonshowdown.com/sprites/ani-back/gimmighoul.gif",
  666. "https://play.pokemonshowdown.com/sprites/ani/gimmighoul-roaming.gif",
  667. "https://play.pokemonshowdown.com/sprites/ani-back/gimmighoul-roaming.gif",
  668. "https://play.pokemonshowdown.com/sprites/ani/gholdengo.gif",
  669. "https://play.pokemonshowdown.com/sprites/ani-back/gholdengo.gif",
  670. "https://play.pokemonshowdown.com/sprites/ani/wochien.gif",
  671. "https://play.pokemonshowdown.com/sprites/ani-back/wochien.gif",
  672. "https://play.pokemonshowdown.com/sprites/ani/chienpao.gif",
  673. "https://play.pokemonshowdown.com/sprites/ani-back/chienpao.gif",
  674. "https://play.pokemonshowdown.com/sprites/ani/tinglu.gif",
  675. "https://play.pokemonshowdown.com/sprites/ani-back/tinglu.gif",
  676. "https://play.pokemonshowdown.com/sprites/ani/chiyu.gif",
  677. "https://play.pokemonshowdown.com/sprites/ani-back/chiyu.gif",
  678. "https://play.pokemonshowdown.com/sprites/ani/roaringmoon.gif",
  679. "https://play.pokemonshowdown.com/sprites/ani-back/roaringmoon.gif",
  680. "https://play.pokemonshowdown.com/sprites/ani/ironvaliant.gif",
  681. "https://play.pokemonshowdown.com/sprites/ani-back/ironvaliant.gif",
  682. "https://play.pokemonshowdown.com/sprites/ani/koraidon.gif",
  683. "https://play.pokemonshowdown.com/sprites/ani-back/koraidon.gif",
  684. "https://play.pokemonshowdown.com/sprites/ani/miraidon.gif",
  685. "https://play.pokemonshowdown.com/sprites/ani-back/miraidon.gif",
  686. "https://play.pokemonshowdown.com/sprites/ani/walkingwake.gif",
  687. "https://play.pokemonshowdown.com/sprites/ani-back/walkingwake.gif",
  688. "https://play.pokemonshowdown.com/sprites/ani/ironleaves.gif",
  689. "https://play.pokemonshowdown.com/sprites/ani-back/ironleaves.gif",
  690. "https://play.pokemonshowdown.com/sprites/ani/dipplin.gif",
  691. "https://play.pokemonshowdown.com/sprites/ani-back/dipplin.gif",
  692. "https://play.pokemonshowdown.com/sprites/ani/poltchageist.gif",
  693. "https://play.pokemonshowdown.com/sprites/ani-back/poltchageist.gif",
  694. "https://play.pokemonshowdown.com/sprites/ani/poltchageist-artisan.gif",
  695. "https://play.pokemonshowdown.com/sprites/ani-back/poltchageist-artisan.gif",
  696. "https://play.pokemonshowdown.com/sprites/ani/sinistcha.gif",
  697. "https://play.pokemonshowdown.com/sprites/ani-back/sinistcha.gif",
  698. "https://play.pokemonshowdown.com/sprites/ani/sinistcha-masterpiece.gif",
  699. "https://play.pokemonshowdown.com/sprites/ani-back/sinistcha-masterpiece.gif",
  700. "https://play.pokemonshowdown.com/sprites/ani/okidogi.gif",
  701. "https://play.pokemonshowdown.com/sprites/ani-back/okidogi.gif",
  702. "https://play.pokemonshowdown.com/sprites/ani/munkidori.gif",
  703. "https://play.pokemonshowdown.com/sprites/ani-back/munkidori.gif",
  704. "https://play.pokemonshowdown.com/sprites/ani/fezandipiti.gif",
  705. "https://play.pokemonshowdown.com/sprites/ani-back/fezandipiti.gif",
  706. "https://play.pokemonshowdown.com/sprites/ani/ogerpon.gif",
  707. "https://play.pokemonshowdown.com/sprites/ani-back/ogerpon.gif",
  708. "https://play.pokemonshowdown.com/sprites/ani/ogerpon-wellspring.gif",
  709. "https://play.pokemonshowdown.com/sprites/ani-back/ogerpon-wellspring.gif",
  710. "https://play.pokemonshowdown.com/sprites/ani/ogerpon-hearthflame.gif",
  711. "https://play.pokemonshowdown.com/sprites/ani-back/ogerpon-hearthflame.gif",
  712. "https://play.pokemonshowdown.com/sprites/ani/ogerpon-cornerstone.gif",
  713. "https://play.pokemonshowdown.com/sprites/ani-back/ogerpon-cornerstone.gif",
  714. "https://play.pokemonshowdown.com/sprites/ani/ogerpon-teal-tera.gif",
  715. "https://play.pokemonshowdown.com/sprites/ani-back/ogerpon-teal-tera.gif",
  716. "https://play.pokemonshowdown.com/sprites/ani/ogerpon-wellspring-tera.gif",
  717. "https://play.pokemonshowdown.com/sprites/ani-back/ogerpon-wellspring-tera.gif",
  718. "https://play.pokemonshowdown.com/sprites/ani/ogerpon-hearthflame-tera.gif",
  719. "https://play.pokemonshowdown.com/sprites/ani-back/ogerpon-hearthflame-tera.gif",
  720. "https://play.pokemonshowdown.com/sprites/ani/ogerpon-cornerstone-tera.gif",
  721. "https://play.pokemonshowdown.com/sprites/ani-back/ogerpon-cornerstone-tera.gif",
  722. ]
  723. KNOWN_MISSING_PNGS = ("vivillon", "furfrou", "alcremie")
  724. async def main(
  725. dex_file: Path, image_dir: Path, startIndex: int, endIndex: int,
  726. log_skipped: bool, force_dex: bool, dex_only: bool, staging: bool,
  727. ):
  728. dex = await load_pokedex(dex_file, force_dex)
  729. if dex_only:
  730. return
  731. log = await download_all(image_dir, (dex[i] for i in range(startIndex, endIndex + 1)), staging)
  732. new_downloads = 0
  733. for form, result in log.items():
  734. for url, info in result.items():
  735. if isinstance(info, Exception):
  736. if url not in KNOWN_MISSING and not (".png" in url and any(s in url for s in KNOWN_MISSING_PNGS)):
  737. print(f"{form}: FAILED {url} - {info}")
  738. elif not info:
  739. if staging:
  740. print(url)
  741. elif log_skipped:
  742. print(f"{form}: SKIPPED {url}")
  743. else:
  744. print(f"{form}: SUCCESS {url}")
  745. new_downloads += 1
  746. print(f"New Downloads: {new_downloads}")
  747. if __name__ == "__main__":
  748. from argparse import ArgumentParser
  749. parser = ArgumentParser(
  750. prog="Image Retriever",
  751. description="Retrieve pokedex and images",
  752. )
  753. parser.add_argument(
  754. "-d", "--pokedex", default="data/pokedex.json", type=Path, help="Pokedex file"
  755. )
  756. parser.add_argument(
  757. "--refresh-dex", action="store_true", help="Update the pokedex"
  758. )
  759. parser.add_argument(
  760. "--pokedex-only", action="store_true", help="Quit before image download"
  761. )
  762. parser.add_argument(
  763. "-s", "--staging", action="store_true", help="Log URLs without downloading"
  764. )
  765. parser.add_argument(
  766. "-o", "--output", default="images", type=Path, help="Image output directory"
  767. )
  768. parser.add_argument(
  769. "--log-skipped", action="store_true", help="Log skipped images"
  770. )
  771. parser.add_argument(
  772. "bounds", type=lambda a: map(int, a.split("-")), default="1-151", nargs="?",
  773. help="Range of dex numbers to download, inclusive"
  774. )
  775. args = parser.parse_args()
  776. start, end = args.bounds
  777. asyncio.run(main(
  778. args.pokedex, args.output, start, end,
  779. args.log_skipped, args.refresh_dex, args.pokedex_only, args.staging
  780. ))