Files
go-eve-pi/esi/client.go
2025-10-11 10:51:52 +02:00

188 lines
5.8 KiB
Go

// 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{}