| import requests |
| from typing import Dict, Any, List, Optional |
| from functools import lru_cache |
| import os |
| import json |
| import re |
|
|
| |
| BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
| DATA_DIR = os.path.join(BASE_DIR, 'data') |
|
|
| |
| POKEMON_ID_MAP = {} |
| try: |
| id_map_path = os.path.join(DATA_DIR, 'pokemon_ids.json') |
| if os.path.exists(id_map_path): |
| with open(id_map_path, 'r') as f: |
| POKEMON_ID_MAP = json.load(f) |
| except Exception: |
| pass |
|
|
| |
| |
| from .pokemon_utils import POKEAPI_NAME_MAP |
|
|
| def get_best_sprite(data, side='front', shiny=False): |
| |
| name = data.get('name', '').lower() |
| |
| |
| if 'zygarde-mega' in name: |
| if side == 'back': |
| return "https://pbs.twimg.com/media/G7U70olbIAAtYXa?format=png&name=360x360" |
| return "https://pbs.twimg.com/media/G3lRUbgXMAA3jdG?format=png&name=small" |
| |
| |
| |
| |
| if '-mega-' in name: |
| name = name.replace('-mega-x', '-megax').replace('-mega-y', '-megay') |
| FLATTEN_BASE = [ |
| 'ho-oh', 'porygon-z', 'jangmo-o', 'hakamo-o', 'kommo-o', |
| 'sirfetch’d', 'farfetch’d', 'sirfetchd', 'farfetchd', |
| 'mr-mime', 'mr-rime', 'mime-jr', 'type-null', |
| 'tapu-koko', 'tapu-lele', 'tapu-bulu', 'tapu-fini', |
| 'nidoran-m', 'nidoran-f' |
| ] |
| |
| |
| SHOWDOWN_OVERRIDES = { |
| 'aegislash-shield': 'aegislash', |
| 'aegislash-blade': 'aegislash-blade', |
| 'basculin-red-striped': 'basculin', |
| 'basculin-blue-striped': 'basculin-blue', |
| 'basculin-white-striped': 'basculin-white', |
| 'darmanitan-standard': 'darmanitan', |
| 'darmanitan-galar-standard': 'darmanitan-galar', |
| 'deoxys-normal': 'deoxys', |
| 'giratina-altered': 'giratina', |
| 'gourgeist-average': 'gourgeist', |
| 'keldeo-ordinary': 'keldeo', |
| 'landorus-incarnate': 'landorus', |
| 'thundurus-incarnate': 'thundurus', |
| 'tornadus-incarnate': 'tornadus', |
| 'meloetta-aria': 'meloetta', |
| 'mimikyu-disguised': 'mimikyu', |
| 'morpeko-full-belly': 'morpeko', |
| 'morpeko-hangry': 'morpeko-hangry', |
| 'oricorio-baile': 'oricorio', |
| 'pumpkaboo-average': 'pumpkaboo', |
| 'shaymin-land': 'shaymin', |
| 'toxtricity-amped': 'toxtricity', |
| 'urshifu-single-strike': 'urshifu', |
| 'wormadam-plant': 'wormadam', |
| 'zygarde-50': 'zygarde', |
| 'minior-red-meteor': 'minior', |
| 'minior-red': 'minior-red', |
| 'wishiwashi-solo': 'wishiwashi', |
| 'wishiwashi-school': 'wishiwashi-school', |
| 'toxtricity-low-key': 'toxtricity-lowkey', |
| 'gastrodon-west': 'gastrodon', |
| 'lycanroc-midday': 'lycanroc' |
| } |
|
|
| pokemon_name = name |
| |
| |
| if name in SHOWDOWN_OVERRIDES: |
| pokemon_name = SHOWDOWN_OVERRIDES[name] |
| elif name in FLATTEN_BASE: |
| pokemon_name = name.replace('-', '') |
| else: |
| |
| pokemon_name = name.replace("'", "").replace("’", "") |
| |
| |
| if ' ' in pokemon_name: |
| pokemon_name = pokemon_name.replace(' ', '') |
|
|
| |
| if pokemon_name.startswith('mr-'): |
| pokemon_name = pokemon_name.replace('-', '') |
| if pokemon_name.endswith('-jr'): |
| pokemon_name = pokemon_name.replace('-', '') |
|
|
| back_suffix = "-back" if side == 'back' else "" |
| shiny_suffix = "-shiny" if shiny else "" |
|
|
| |
| url_pixel_static = f"https://play.pokemonshowdown.com/sprites/gen5{back_suffix}{shiny_suffix}/{pokemon_name}.png" |
| |
| return url_pixel_static |
|
|
| @lru_cache(maxsize=1000) |
| def get_pokemon_data(pokemon_name): |
| """Fetch Pokemon data from PokeAPI with caching and name normalization.""" |
| |
| normalized_name = re.sub(r'[^a-z0-9]', '', pokemon_name.lower()) |
| api_name = POKEAPI_NAME_MAP.get(normalized_name, None) |
| |
| |
| if not api_name: |
| |
| cleaned_name = pokemon_name.lower().replace('%', '').strip() |
| api_name = cleaned_name if '-' in cleaned_name else normalized_name |
|
|
| |
| pokemon_id = POKEMON_ID_MAP.get(api_name) or POKEMON_ID_MAP.get(normalized_name) |
| identifier = str(pokemon_id) if pokemon_id else api_name |
|
|
| url = f'https://pokeapi.co/api/v2/pokemon/{identifier}' |
| try: |
| response = requests.get(url, timeout=5) |
| if response.status_code == 200: |
| return response.json() |
| except Exception: |
| pass |
| |
| |
| patterns = [ |
| f"{api_name}-galar", f"{api_name}-alola", f"{api_name}-hisui", |
| f"{api_name}-origin", f"{api_name}-altered", f"{api_name}-single-strike", |
| f"{api_name}-amped", f"{api_name}-low-key" |
| ] |
| for p_name in patterns: |
| try: |
| res = requests.get(f'https://pokeapi.co/api/v2/pokemon/{p_name}', timeout=5) |
| if res.status_code == 200: |
| return res.json() |
| except: |
| continue |
| |
| |
| if '-' in api_name: |
| base_name = api_name.split('-')[0] |
| try: |
| res = requests.get(f'https://pokeapi.co/api/v2/pokemon/{base_name}', timeout=5) |
| if res.status_code == 200: |
| return res.json() |
| except: |
| pass |
| |
| return None |
|
|
| def get_forme_data(species_name: str, side='front', shiny=False): |
| """Helper for mid-battle form changes to get all necessary transformation data.""" |
| data = get_pokemon_data(species_name) |
| if not data: |
| return None |
| |
| primary_ability = next((a['ability']['name'].replace('-', '').lower() for a in data.get('abilities', []) if not a.get('is_hidden')), '') |
| if not primary_ability and data.get('abilities'): |
| primary_ability = data['abilities'][0]['ability']['name'].replace('-', '').lower() |
| |
| return { |
| 'name': data['name'], |
| 'types': [t['type']['name'] for t in data['types']], |
| 'sprite_url': get_best_sprite(data, side=side, shiny=shiny), |
| 'cry_url': data.get('cries', {}).get('latest', ''), |
| 'stats': {s['stat']['name'].replace('-', '_'): s['base_stat'] for s in data['stats']}, |
| 'ability': primary_ability |
| } |
|
|
| def to_display_name(name: str) -> str: |
| """Format a Pokémon name for display (e.g. 'tapu-koko' -> 'Tapu-Koko').""" |
| |
| if name.lower() == 'urshifu-single-strike': return 'Urshifu-Single-Strike' |
| if name.lower() == 'urshifu': return 'Urshifu-Single-Strike' |
| if name.lower() == 'giratina-altered': return 'Giratina-Altered' |
| if name.lower() == 'giratina': return 'Giratina-Altered' |
| |
| |
| parts = re.split(r'[- ]', name) |
| return '-'.join(p.capitalize() for p in parts) |
|
|