Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
422 changes: 422 additions & 0 deletions badge/apps/pokedex/__init__.py

Large diffs are not rendered by default.

87 changes: 87 additions & 0 deletions badge/apps/pokedex/fetch_sprites.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""Download Gen 1 Pokemon sprites for the badge Pokedex app.

Fetches normal and shiny sprites (64x64 PNG) from PokeAPI via wsrv.nl proxy.
Sprites are Nintendo/Game Freak/The Pokemon Company's intellectual property
and are NOT included in this repository. This script fetches them for
personal use on your badge.

Usage:
python3 fetch_sprites.py # Download to ./sprites/ and ./shiny/
python3 fetch_sprites.py /Volumes/BADGER # Download directly to badge
"""

import os
import sys
import subprocess
from concurrent.futures import ThreadPoolExecutor, as_completed

BASE_URL = "https://wsrv.nl/?url=https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{path}{id}.png&w=80&h=80&output=png"
MAX_POKEMON = 151
WORKERS = 10


def fetch_sprite(pokemon_id, output_dir, shiny=False):
path = "shiny/" if shiny else ""
subdir = "shiny" if shiny else "sprites"
out_dir = os.path.join(output_dir, subdir)
os.makedirs(out_dir, exist_ok=True)
out_path = os.path.join(out_dir, f"{pokemon_id}.png")

if os.path.exists(out_path):
return pokemon_id, shiny, True

url = BASE_URL.format(id=pokemon_id, path=path)
try:
result = subprocess.run(
["curl", "-sf", url, "-o", out_path],
capture_output=True, timeout=30,
)
return pokemon_id, shiny, result.returncode == 0
except Exception:
return pokemon_id, shiny, False
Comment on lines +36 to +42

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script shells out to curl, which isn't available by default on all platforms (notably some Windows setups) and makes error handling harder. Consider using Python's standard library (urllib.request) for downloads so the setup flow works anywhere python3 runs.

Copilot uses AI. Check for mistakes.


def main():
output_dir = sys.argv[1] if len(sys.argv) > 1 else "."

tasks = []
for pid in range(1, MAX_POKEMON + 1):
tasks.append((pid, False)) # normal
tasks.append((pid, True)) # shiny

total = len(tasks)
done = 0
failed = []

print(f"Fetching {total} sprites ({MAX_POKEMON} normal + {MAX_POKEMON} shiny)...")

with ThreadPoolExecutor(max_workers=WORKERS) as executor:
futures = {
executor.submit(fetch_sprite, pid, output_dir, is_shiny): (pid, is_shiny)
for pid, is_shiny in tasks
}
for future in as_completed(futures):
pid, is_shiny, ok = future.result()
done += 1
label = "shiny" if is_shiny else "normal"
if not ok:
failed.append((pid, label))
print(f" FAILED: {label} #{pid}")
if done % 50 == 0:
print(f" {done}/{total} complete...")

print(f"\nDone! {done - len(failed)}/{total} sprites downloaded.")
if failed:
print(f"{len(failed)} failed: {failed}")

# Verify
sprites_dir = os.path.join(output_dir, "sprites")
shiny_dir = os.path.join(output_dir, "shiny")
normal_count = len(os.listdir(sprites_dir)) if os.path.isdir(sprites_dir) else 0
shiny_count = len(os.listdir(shiny_dir)) if os.path.isdir(shiny_dir) else 0
print(f"Normal: {normal_count}, Shiny: {shiny_count}")


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions badge/apps/pokedex/gen1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"1":{"n":"bulbasaur","t":["grass","poison"],"s":[45,49,49,65,65,45],"h":7,"w":69},"2":{"n":"ivysaur","t":["grass","poison"],"s":[60,62,63,80,80,60],"h":10,"w":130},"3":{"n":"venusaur","t":["grass","poison"],"s":[80,82,83,100,100,80],"h":20,"w":1000},"4":{"n":"charmander","t":["fire"],"s":[39,52,43,60,50,65],"h":6,"w":85},"5":{"n":"charmeleon","t":["fire"],"s":[58,64,58,80,65,80],"h":11,"w":190},"6":{"n":"charizard","t":["fire","flying"],"s":[78,84,78,109,85,100],"h":17,"w":905},"7":{"n":"squirtle","t":["water"],"s":[44,48,65,50,64,43],"h":5,"w":90},"8":{"n":"wartortle","t":["water"],"s":[59,63,80,65,80,58],"h":10,"w":225},"9":{"n":"blastoise","t":["water"],"s":[79,83,100,85,105,78],"h":16,"w":855},"10":{"n":"caterpie","t":["bug"],"s":[45,30,35,20,20,45],"h":3,"w":29},"11":{"n":"metapod","t":["bug"],"s":[50,20,55,25,25,30],"h":7,"w":99},"12":{"n":"butterfree","t":["bug","flying"],"s":[60,45,50,90,80,70],"h":11,"w":320},"13":{"n":"weedle","t":["bug","poison"],"s":[40,35,30,20,20,50],"h":3,"w":32},"14":{"n":"kakuna","t":["bug","poison"],"s":[45,25,50,25,25,35],"h":6,"w":100},"15":{"n":"beedrill","t":["bug","poison"],"s":[65,90,40,45,80,75],"h":10,"w":295},"16":{"n":"pidgey","t":["normal","flying"],"s":[40,45,40,35,35,56],"h":3,"w":18},"17":{"n":"pidgeotto","t":["normal","flying"],"s":[63,60,55,50,50,71],"h":11,"w":300},"18":{"n":"pidgeot","t":["normal","flying"],"s":[83,80,75,70,70,101],"h":15,"w":395},"19":{"n":"rattata","t":["normal"],"s":[30,56,35,25,35,72],"h":3,"w":35},"20":{"n":"raticate","t":["normal"],"s":[55,81,60,50,70,97],"h":7,"w":185},"21":{"n":"spearow","t":["normal","flying"],"s":[40,60,30,31,31,70],"h":3,"w":20},"22":{"n":"fearow","t":["normal","flying"],"s":[65,90,65,61,61,100],"h":12,"w":380},"23":{"n":"ekans","t":["poison"],"s":[35,60,44,40,54,55],"h":20,"w":69},"24":{"n":"arbok","t":["poison"],"s":[60,95,69,65,79,80],"h":35,"w":650},"25":{"n":"pikachu","t":["electric"],"s":[35,55,40,50,50,90],"h":4,"w":60},"26":{"n":"raichu","t":["electric"],"s":[60,90,55,90,80,110],"h":8,"w":300},"27":{"n":"sandshrew","t":["ground"],"s":[50,75,85,20,30,40],"h":6,"w":120},"28":{"n":"sandslash","t":["ground"],"s":[75,100,110,45,55,65],"h":10,"w":295},"29":{"n":"nidoran-f","t":["poison"],"s":[55,47,52,40,40,41],"h":4,"w":70},"30":{"n":"nidorina","t":["poison"],"s":[70,62,67,55,55,56],"h":8,"w":200},"31":{"n":"nidoqueen","t":["poison","ground"],"s":[90,92,87,75,85,76],"h":13,"w":600},"32":{"n":"nidoran-m","t":["poison"],"s":[46,57,40,40,40,50],"h":5,"w":90},"33":{"n":"nidorino","t":["poison"],"s":[61,72,57,55,55,65],"h":9,"w":195},"34":{"n":"nidoking","t":["poison","ground"],"s":[81,102,77,85,75,85],"h":14,"w":620},"35":{"n":"clefairy","t":["fairy"],"s":[70,45,48,60,65,35],"h":6,"w":75},"36":{"n":"clefable","t":["fairy"],"s":[95,70,73,95,90,60],"h":13,"w":400},"37":{"n":"vulpix","t":["fire"],"s":[38,41,40,50,65,65],"h":6,"w":99},"38":{"n":"ninetales","t":["fire"],"s":[73,76,75,81,100,100],"h":11,"w":199},"39":{"n":"jigglypuff","t":["normal","fairy"],"s":[115,45,20,45,25,20],"h":5,"w":55},"40":{"n":"wigglytuff","t":["normal","fairy"],"s":[140,70,45,85,50,45],"h":10,"w":120},"41":{"n":"zubat","t":["poison","flying"],"s":[40,45,35,30,40,55],"h":8,"w":75},"42":{"n":"golbat","t":["poison","flying"],"s":[75,80,70,65,75,90],"h":16,"w":550},"43":{"n":"oddish","t":["grass","poison"],"s":[45,50,55,75,65,30],"h":5,"w":54},"44":{"n":"gloom","t":["grass","poison"],"s":[60,65,70,85,75,40],"h":8,"w":86},"45":{"n":"vileplume","t":["grass","poison"],"s":[75,80,85,110,90,50],"h":12,"w":186},"46":{"n":"paras","t":["bug","grass"],"s":[35,70,55,45,55,25],"h":3,"w":54},"47":{"n":"parasect","t":["bug","grass"],"s":[60,95,80,60,80,30],"h":10,"w":295},"48":{"n":"venonat","t":["bug","poison"],"s":[60,55,50,40,55,45],"h":10,"w":300},"49":{"n":"venomoth","t":["bug","poison"],"s":[70,65,60,90,75,90],"h":15,"w":125},"50":{"n":"diglett","t":["ground"],"s":[10,55,25,35,45,95],"h":2,"w":8},"51":{"n":"dugtrio","t":["ground"],"s":[35,100,50,50,70,120],"h":7,"w":333},"52":{"n":"meowth","t":["normal"],"s":[40,45,35,40,40,90],"h":4,"w":42},"53":{"n":"persian","t":["normal"],"s":[65,70,60,65,65,115],"h":10,"w":320},"54":{"n":"psyduck","t":["water"],"s":[50,52,48,65,50,55],"h":8,"w":196},"55":{"n":"golduck","t":["water"],"s":[80,82,78,95,80,85],"h":17,"w":766},"56":{"n":"mankey","t":["fighting"],"s":[40,80,35,35,45,70],"h":5,"w":280},"57":{"n":"primeape","t":["fighting"],"s":[65,105,60,60,70,95],"h":10,"w":320},"58":{"n":"growlithe","t":["fire"],"s":[55,70,45,70,50,60],"h":7,"w":190},"59":{"n":"arcanine","t":["fire"],"s":[90,110,80,100,80,95],"h":19,"w":1550},"60":{"n":"poliwag","t":["water"],"s":[40,50,40,40,40,90],"h":6,"w":124},"61":{"n":"poliwhirl","t":["water"],"s":[65,65,65,50,50,90],"h":10,"w":200},"62":{"n":"poliwrath","t":["water","fighting"],"s":[90,95,95,70,90,70],"h":13,"w":540},"63":{"n":"abra","t":["psychic"],"s":[25,20,15,105,55,90],"h":9,"w":195},"64":{"n":"kadabra","t":["psychic"],"s":[40,35,30,120,70,105],"h":13,"w":565},"65":{"n":"alakazam","t":["psychic"],"s":[55,50,45,135,95,120],"h":15,"w":480},"66":{"n":"machop","t":["fighting"],"s":[70,80,50,35,35,35],"h":8,"w":195},"67":{"n":"machoke","t":["fighting"],"s":[80,100,70,50,60,45],"h":15,"w":705},"68":{"n":"machamp","t":["fighting"],"s":[90,130,80,65,85,55],"h":16,"w":1300},"69":{"n":"bellsprout","t":["grass","poison"],"s":[50,75,35,70,30,40],"h":7,"w":40},"70":{"n":"weepinbell","t":["grass","poison"],"s":[65,90,50,85,45,55],"h":10,"w":64},"71":{"n":"victreebel","t":["grass","poison"],"s":[80,105,65,100,70,70],"h":17,"w":155},"72":{"n":"tentacool","t":["water","poison"],"s":[40,40,35,50,100,70],"h":9,"w":455},"73":{"n":"tentacruel","t":["water","poison"],"s":[80,70,65,80,120,100],"h":16,"w":550},"74":{"n":"geodude","t":["rock","ground"],"s":[40,80,100,30,30,20],"h":4,"w":200},"75":{"n":"graveler","t":["rock","ground"],"s":[55,95,115,45,45,35],"h":10,"w":1050},"76":{"n":"golem","t":["rock","ground"],"s":[80,120,130,55,65,45],"h":14,"w":3000},"77":{"n":"ponyta","t":["fire"],"s":[50,85,55,65,65,90],"h":10,"w":300},"78":{"n":"rapidash","t":["fire"],"s":[65,100,70,80,80,105],"h":17,"w":950},"79":{"n":"slowpoke","t":["water","psychic"],"s":[90,65,65,40,40,15],"h":12,"w":360},"80":{"n":"slowbro","t":["water","psychic"],"s":[95,75,110,100,80,30],"h":16,"w":785},"81":{"n":"magnemite","t":["electric","steel"],"s":[25,35,70,95,55,45],"h":3,"w":60},"82":{"n":"magneton","t":["electric","steel"],"s":[50,60,95,120,70,70],"h":10,"w":600},"83":{"n":"farfetchd","t":["normal","flying"],"s":[52,90,55,58,62,60],"h":8,"w":150},"84":{"n":"doduo","t":["normal","flying"],"s":[35,85,45,35,35,75],"h":14,"w":392},"85":{"n":"dodrio","t":["normal","flying"],"s":[60,110,70,60,60,110],"h":18,"w":852},"86":{"n":"seel","t":["water"],"s":[65,45,55,45,70,45],"h":11,"w":900},"87":{"n":"dewgong","t":["water","ice"],"s":[90,70,80,70,95,70],"h":17,"w":1200},"88":{"n":"grimer","t":["poison"],"s":[80,80,50,40,50,25],"h":9,"w":300},"89":{"n":"muk","t":["poison"],"s":[105,105,75,65,100,50],"h":12,"w":300},"90":{"n":"shellder","t":["water"],"s":[30,65,100,45,25,40],"h":3,"w":40},"91":{"n":"cloyster","t":["water","ice"],"s":[50,95,180,85,45,70],"h":15,"w":1325},"92":{"n":"gastly","t":["ghost","poison"],"s":[30,35,30,100,35,80],"h":13,"w":1},"93":{"n":"haunter","t":["ghost","poison"],"s":[45,50,45,115,55,95],"h":16,"w":1},"94":{"n":"gengar","t":["ghost","poison"],"s":[60,65,60,130,75,110],"h":15,"w":405},"95":{"n":"onix","t":["rock","ground"],"s":[35,45,160,30,45,70],"h":88,"w":2100},"96":{"n":"drowzee","t":["psychic"],"s":[60,48,45,43,90,42],"h":10,"w":324},"97":{"n":"hypno","t":["psychic"],"s":[85,73,70,73,115,67],"h":16,"w":756},"98":{"n":"krabby","t":["water"],"s":[30,105,90,25,25,50],"h":4,"w":65},"99":{"n":"kingler","t":["water"],"s":[55,130,115,50,50,75],"h":13,"w":600},"100":{"n":"voltorb","t":["electric"],"s":[40,30,50,55,55,100],"h":5,"w":104},"101":{"n":"electrode","t":["electric"],"s":[60,50,70,80,80,150],"h":12,"w":666},"102":{"n":"exeggcute","t":["grass","psychic"],"s":[60,40,80,60,45,40],"h":4,"w":25},"103":{"n":"exeggutor","t":["grass","psychic"],"s":[95,95,85,125,75,55],"h":20,"w":1200},"104":{"n":"cubone","t":["ground"],"s":[50,50,95,40,50,35],"h":4,"w":65},"105":{"n":"marowak","t":["ground"],"s":[60,80,110,50,80,45],"h":10,"w":450},"106":{"n":"hitmonlee","t":["fighting"],"s":[50,120,53,35,110,87],"h":15,"w":498},"107":{"n":"hitmonchan","t":["fighting"],"s":[50,105,79,35,110,76],"h":14,"w":502},"108":{"n":"lickitung","t":["normal"],"s":[90,55,75,60,75,30],"h":12,"w":655},"109":{"n":"koffing","t":["poison"],"s":[40,65,95,60,45,35],"h":6,"w":10},"110":{"n":"weezing","t":["poison"],"s":[65,90,120,85,70,60],"h":12,"w":95},"111":{"n":"rhyhorn","t":["ground","rock"],"s":[80,85,95,30,30,25],"h":10,"w":1150},"112":{"n":"rhydon","t":["ground","rock"],"s":[105,130,120,45,45,40],"h":19,"w":1200},"113":{"n":"chansey","t":["normal"],"s":[250,5,5,35,105,50],"h":11,"w":346},"114":{"n":"tangela","t":["grass"],"s":[65,55,115,100,40,60],"h":10,"w":350},"115":{"n":"kangaskhan","t":["normal"],"s":[105,95,80,40,80,90],"h":22,"w":800},"116":{"n":"horsea","t":["water"],"s":[30,40,70,70,25,60],"h":4,"w":80},"117":{"n":"seadra","t":["water"],"s":[55,65,95,95,45,85],"h":12,"w":250},"118":{"n":"goldeen","t":["water"],"s":[45,67,60,35,50,63],"h":6,"w":150},"119":{"n":"seaking","t":["water"],"s":[80,92,65,65,80,68],"h":13,"w":390},"120":{"n":"staryu","t":["water"],"s":[30,45,55,70,55,85],"h":8,"w":345},"121":{"n":"starmie","t":["water","psychic"],"s":[60,75,85,100,85,115],"h":11,"w":800},"122":{"n":"mr-mime","t":["psychic","fairy"],"s":[40,45,65,100,120,90],"h":13,"w":545},"123":{"n":"scyther","t":["bug","flying"],"s":[70,110,80,55,80,105],"h":15,"w":560},"124":{"n":"jynx","t":["ice","psychic"],"s":[65,50,35,115,95,95],"h":14,"w":406},"125":{"n":"electabuzz","t":["electric"],"s":[65,83,57,95,85,105],"h":11,"w":300},"126":{"n":"magmar","t":["fire"],"s":[65,95,57,100,85,93],"h":13,"w":445},"127":{"n":"pinsir","t":["bug"],"s":[65,125,100,55,70,85],"h":15,"w":550},"128":{"n":"tauros","t":["normal"],"s":[75,100,95,40,70,110],"h":14,"w":884},"129":{"n":"magikarp","t":["water"],"s":[20,10,55,15,20,80],"h":9,"w":100},"130":{"n":"gyarados","t":["water","flying"],"s":[95,125,79,60,100,81],"h":65,"w":2350},"131":{"n":"lapras","t":["water","ice"],"s":[130,85,80,85,95,60],"h":25,"w":2200},"132":{"n":"ditto","t":["normal"],"s":[48,48,48,48,48,48],"h":3,"w":40},"133":{"n":"eevee","t":["normal"],"s":[55,55,50,45,65,55],"h":3,"w":65},"134":{"n":"vaporeon","t":["water"],"s":[130,65,60,110,95,65],"h":10,"w":290},"135":{"n":"jolteon","t":["electric"],"s":[65,65,60,110,95,130],"h":8,"w":245},"136":{"n":"flareon","t":["fire"],"s":[65,130,60,95,110,65],"h":9,"w":250},"137":{"n":"porygon","t":["normal"],"s":[65,60,70,85,75,40],"h":8,"w":365},"138":{"n":"omanyte","t":["rock","water"],"s":[35,40,100,90,55,35],"h":4,"w":75},"139":{"n":"omastar","t":["rock","water"],"s":[70,60,125,115,70,55],"h":10,"w":350},"140":{"n":"kabuto","t":["rock","water"],"s":[30,80,90,55,45,55],"h":5,"w":115},"141":{"n":"kabutops","t":["rock","water"],"s":[60,115,105,65,70,80],"h":13,"w":405},"142":{"n":"aerodactyl","t":["rock","flying"],"s":[80,105,65,60,75,130],"h":18,"w":590},"143":{"n":"snorlax","t":["normal"],"s":[160,110,65,65,110,30],"h":21,"w":4600},"144":{"n":"articuno","t":["ice","flying"],"s":[90,85,100,95,125,85],"h":17,"w":554},"145":{"n":"zapdos","t":["electric","flying"],"s":[90,90,85,125,90,100],"h":16,"w":526},"146":{"n":"moltres","t":["fire","flying"],"s":[90,100,90,125,85,90],"h":20,"w":600},"147":{"n":"dratini","t":["dragon"],"s":[41,64,45,50,50,50],"h":18,"w":33},"148":{"n":"dragonair","t":["dragon"],"s":[61,84,65,70,70,70],"h":40,"w":165},"149":{"n":"dragonite","t":["dragon","flying"],"s":[91,134,95,100,100,80],"h":22,"w":2100},"150":{"n":"mewtwo","t":["psychic"],"s":[106,110,90,154,90,130],"h":20,"w":1220},"151":{"n":"mew","t":["psychic"],"s":[100,100,100,100,100,100],"h":4,"w":40},"0":{"n":"keefymon","t":["fighting","fairy"],"s":[154,154,154,154,154,154],"h":18,"w":900}}
Binary file added badge/apps/pokedex/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added badge/apps/pokedex/keefymon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added badge/apps/pokedex/keefymon_shiny.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added badge/apps/pokedex/screenshot_charmander.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added badge/apps/pokedex/screenshot_mewtwo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added badge/apps/pokedex/screenshot_pikachu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added badge/apps/pokedex/screenshot_shiny_pikachu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading