""" Entropia Nexus API Client - Production Implementation Uses the real Entropia Nexus API endpoints: - https://api.entropianexus.com/weapons - https://api.entropianexus.com/armors - https://api.entropianexus.com/tools """ import asyncio import json import logging from dataclasses import dataclass, field from decimal import Decimal from typing import Dict, List, Optional, Any from datetime import datetime # Optional HTTP libraries try: import aiohttp HAS_AIOHTTP = True except ImportError: HAS_AIOHTTP = False try: import urllib.request HAS_URLLIB = True except ImportError: HAS_URLLIB = False logger = logging.getLogger(__name__) @dataclass class WeaponStats: """Weapon statistics from Entropia Nexus API.""" id: int item_id: int name: str type: str category: str weapon_class: str weight: Decimal uses_per_minute: int range_meters: Optional[Decimal] efficiency: Optional[Decimal] max_tt: Decimal min_tt: Optional[Decimal] decay: Optional[Decimal] ammo_burn: Optional[int] damage_stab: Decimal damage_cut: Decimal damage_impact: Decimal damage_penetration: Decimal damage_shrapnel: Decimal damage_burn: Decimal damage_cold: Decimal damage_acid: Decimal damage_electric: Decimal total_damage: Decimal = field(default_factory=lambda: Decimal("0")) dpp: Decimal = field(default_factory=lambda: Decimal("0")) cost_per_hour: Decimal = field(default_factory=lambda: Decimal("0")) sib: bool = False hit_skill_start: Optional[int] = None hit_skill_end: Optional[int] = None dmg_skill_start: Optional[int] = None dmg_skill_end: Optional[int] = None ammo_name: Optional[str] = None api_url: str = "" def __post_init__(self): """Calculate derived statistics.""" self.total_damage = ( self.damage_stab + self.damage_cut + self.damage_impact + self.damage_penetration + self.damage_shrapnel + self.damage_burn + self.damage_cold + self.damage_acid + self.damage_electric ) if self.decay and self.decay > 0: ammo_cost = Decimal(self.ammo_burn) if self.ammo_burn else Decimal("0") total_cost_pec = self.decay + ammo_cost if total_cost_pec > 0: self.dpp = self.total_damage / total_cost_pec if self.uses_per_minute and self.uses_per_minute > 0 and self.decay: uses_per_hour = self.uses_per_minute * 60 total_decay_per_hour = self.decay * uses_per_hour self.cost_per_hour = total_decay_per_hour / 100 @classmethod def from_api_data(cls, data: Dict[str, Any]) -> 'WeaponStats': """Create WeaponStats from API response.""" props = data.get('Properties', {}) economy = props.get('Economy', {}) damage = props.get('Damage', {}) skill = props.get('Skill', {}) hit_skill = skill.get('Hit', {}) dmg_skill = skill.get('Dmg', {}) ammo = data.get('Ammo', {}) links = data.get('Links', {}) return cls( id=data.get('Id', 0), item_id=data.get('ItemId', 0), name=data.get('Name', 'Unknown'), type=props.get('Type', 'Unknown'), category=props.get('Category', 'Unknown'), weapon_class=props.get('Class', 'Unknown'), weight=Decimal(str(props.get('Weight', 0))) if props.get('Weight') else Decimal('0'), uses_per_minute=props.get('UsesPerMinute', 0) or 0, range_meters=Decimal(str(props.get('Range'))) if props.get('Range') else None, efficiency=Decimal(str(economy.get('Efficiency'))) if economy.get('Efficiency') else None, max_tt=Decimal(str(economy.get('MaxTT', 0))) if economy.get('MaxTT') else Decimal('0'), min_tt=Decimal(str(economy.get('MinTT'))) if economy.get('MinTT') else None, decay=Decimal(str(economy.get('Decay'))) if economy.get('Decay') else None, ammo_burn=economy.get('AmmoBurn'), damage_stab=Decimal(str(damage.get('Stab', 0))) if damage.get('Stab') else Decimal('0'), damage_cut=Decimal(str(damage.get('Cut', 0))) if damage.get('Cut') else Decimal('0'), damage_impact=Decimal(str(damage.get('Impact', 0))) if damage.get('Impact') else Decimal('0'), damage_penetration=Decimal(str(damage.get('Penetration', 0))) if damage.get('Penetration') else Decimal('0'), damage_shrapnel=Decimal(str(damage.get('Shrapnel', 0))) if damage.get('Shrapnel') else Decimal('0'), damage_burn=Decimal(str(damage.get('Burn', 0))) if damage.get('Burn') else Decimal('0'), damage_cold=Decimal(str(damage.get('Cold', 0))) if damage.get('Cold') else Decimal('0'), damage_acid=Decimal(str(damage.get('Acid', 0))) if damage.get('Acid') else Decimal('0'), damage_electric=Decimal(str(damage.get('Electric', 0))) if damage.get('Electric') else Decimal('0'), sib=skill.get('IsSiB', False), hit_skill_start=hit_skill.get('LearningIntervalStart'), hit_skill_end=hit_skill.get('LearningIntervalEnd'), dmg_skill_start=dmg_skill.get('LearningIntervalStart'), dmg_skill_end=dmg_skill.get('LearningIntervalEnd'), ammo_name=ammo.get('Name'), api_url=links.get('$Url', ''), ) class EntropiaNexusAPI: """Client for Entropia Nexus API.""" BASE_URL = "https://api.entropianexus.com" def __init__(self, cache_ttl: int = 3600): self._cache: Dict[str, Any] = {} self._cache_timestamps: Dict[str, float] = {} self._cache_ttl = cache_ttl self._session = None self._weapons_cache: Optional[List[WeaponStats]] = None logger.info("EntropiaNexusAPI initialized") async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): return False async def _make_request(self, endpoint: str) -> Optional[List[Dict]]: """Make API request.""" cache_key = endpoint if cache_key in self._cache: timestamp = self._cache_timestamps.get(cache_key, 0) if (datetime.now().timestamp() - timestamp) < self._cache_ttl: return self._cache[cache_key] url = f"{self.BASE_URL}/{endpoint}" try: if HAS_URLLIB: req = urllib.request.Request( url, headers={'Accept': 'application/json', 'User-Agent': 'Lemontropia-Suite/0.2.0'} ) with urllib.request.urlopen(req, timeout=30) as response: data = json.loads(response.read().decode('utf-8')) self._cache[cache_key] = data self._cache_timestamps[cache_key] = datetime.now().timestamp() return data except Exception as e: logger.error(f"API request failed: {e}") return None async def get_all_weapons(self, force_refresh: bool = False) -> List[WeaponStats]: """Get all weapons.""" if not force_refresh and self._weapons_cache: return self._weapons_cache data = await self._make_request('weapons') if data: self._weapons_cache = [WeaponStats.from_api_data(w) for w in data] return self._weapons_cache return [] async def search_weapons(self, query: str) -> List[WeaponStats]: """Search weapons by name.""" weapons = await self.get_all_weapons() query_lower = query.lower() return [w for w in weapons if query_lower in w.name.lower()] async def get_weapon_by_id(self, weapon_id: int) -> Optional[WeaponStats]: """Get weapon by ID.""" weapons = await self.get_all_weapons() for w in weapons: if w.id == weapon_id or w.item_id == weapon_id: return w return None __all__ = ['WeaponStats', 'EntropiaNexusAPI']