// Package esi provides EVE Online ESI API implementations for planetary interaction data. package esi import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "os" "time" "go-eve-pi/options" logger "git.site.quack-lab.dev/dave/cylogger" ) const ( ESIBaseURL = "https://esi.evetech.net" ) type PlanetName struct { Name string `json:"name"` PlanetID int `json:"planet_id"` SystemID int `json:"system_id"` TypeID int `json:"type_id"` Position struct { X float64 `json:"x"` Y float64 `json:"y"` Z float64 `json:"z"` } `json:"position"` } type FactoryDetails struct { SchematicID int `json:"schematic_id"` } // ESIInterface defines the contract for ESI API interactions type ESIInterface interface { GetCharacterPlanets(ctx context.Context, characterID int, accessToken string) ([]Planet, error) GetPlanetPI(ctx context.Context, characterID int, planetID int64, accessToken string) (*PlanetPI, error) GetPlanetName(ctx context.Context, planetID int64) (*PlanetName, error) } // DirectESI implements ESIInterface with direct API calls type DirectESI struct { httpClient *http.Client } // NewDirectESI creates a new DirectESI instance func NewDirectESI() *DirectESI { // Parse HTTP timeout ONCE at initialization httpTimeout, err := time.ParseDuration(options.GlobalOptions.HTTPTimeout) if err != nil { logger.Error("Invalid HTTP timeout duration %s: %v", options.GlobalOptions.HTTPTimeout, err) os.Exit(1) } return &DirectESI{ httpClient: &http.Client{ Timeout: httpTimeout, }, } } // GetCharacterPlanets retrieves a list of planets for a character func (d *DirectESI) GetCharacterPlanets(ctx context.Context, characterID int, accessToken string) ([]Planet, error) { funclog := logger.Default.WithPrefix("DirectESI.GetCharacterPlanets").WithPrefix(fmt.Sprintf("characterID=%d", characterID)) funclog.Debug("Fetching planets") url := fmt.Sprintf("%s/v3/characters/%d/planets/", ESIBaseURL, characterID) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { funclog.Error("Failed to create request for character planets: %v", err) return nil, err } req.Header.Set("Authorization", "Bearer "+accessToken) req.Header.Set("Accept", "application/json") resp, err := d.httpClient.Do(req) if err != nil { funclog.Error("Failed to fetch character planets: %v", err) return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) funclog.Error("Character planets API returned status %d: %s", resp.StatusCode, string(body)) return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body)) } var planets []Planet if err := json.NewDecoder(resp.Body).Decode(&planets); err != nil { funclog.Error("Failed to decode character planets response: %v", err) return nil, err } funclog.Info("Successfully fetched %d planets", len(planets)) return planets, nil } // GetPlanetPI retrieves detailed information about a specific planet func (d *DirectESI) GetPlanetPI(ctx context.Context, characterID int, planetID int64, accessToken string) (*PlanetPI, error) { funclog := logger.Default.WithPrefix("DirectESI.GetPlanetPI").WithPrefix(fmt.Sprintf("characterID=%d", characterID)).WithPrefix(fmt.Sprintf("planetID=%d", planetID)) funclog.Debug("Fetching planet details") url := fmt.Sprintf("%s/v3/characters/%d/planets/%d/", ESIBaseURL, characterID, planetID) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { funclog.Error("Failed to create request for planet details: %v", err) return nil, err } req.Header.Set("Authorization", "Bearer "+accessToken) req.Header.Set("Accept", "application/json") resp, err := d.httpClient.Do(req) if err != nil { funclog.Error("Failed to fetch planet details: %v", err) return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) funclog.Error("Planet details API returned status %d: %s", resp.StatusCode, string(body)) return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body)) } var pi PlanetPI if err := json.NewDecoder(resp.Body).Decode(&pi); err != nil { funclog.Error("Failed to decode planet details response: %v", err) return nil, err } funclog.Info("Successfully fetched planet details") return &pi, nil } // GetPlanetName retrieves planet name from universe endpoint func (d *DirectESI) GetPlanetName(ctx context.Context, planetID int64) (*PlanetName, error) { funclog := logger.Default.WithPrefix("DirectESI.GetPlanetName").WithPrefix(fmt.Sprintf("planetID=%d", planetID)) funclog.Debug("Fetching planet name") url := fmt.Sprintf("%s/v1/universe/planets/%d/", ESIBaseURL, planetID) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { funclog.Error("Failed to create request for planet name: %v", err) return nil, err } req.Header.Set("Accept", "application/json") resp, err := d.httpClient.Do(req) if err != nil { funclog.Error("Failed to fetch planet name: %v", err) return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { funclog.Error("Failed to read planet name response body: %v", err) return nil, err } if resp.StatusCode != http.StatusOK { funclog.Error("API request failed with status %d: %s", resp.StatusCode, string(body)) return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body)) } var planetName PlanetName if err := json.NewDecoder(bytes.NewReader(body)).Decode(&planetName); err != nil { funclog.Error("Failed to decode planet name response: %v", err) return nil, err } funclog.Info("Successfully fetched planet name: %s", planetName.Name) return &planetName, nil } var _ ESIInterface = &DirectESI{}