package esi import ( "encoding/json" "fmt" "io" "os" "strconv" "time" ) type WaypointResult struct { RequestedCount int `json:"requested_count"` SuccessCount int `json:"success_count"` AppliedWaypoints []int `json:"applied_waypoints"` FailedWaypoints []int `json:"failed_waypoints"` Errors []string `json:"errors"` Success bool `json:"success"` } type WalletTransaction struct { TransactionID int `json:"transaction_id"` Date time.Time `json:"date"` LocationID int `json:"location_id"` TypeID int `json:"type_id"` UnitPrice float64 `json:"unit_price"` Quantity int `json:"quantity"` ClientID int `json:"client_id"` IsBuy bool `json:"is_buy"` IsPersonal bool `json:"is_personal"` JournalRefID int `json:"journal_ref_id"` } type LocationData struct { SolarSystemID int `json:"solar_system_id"` StationID int `json:"station_id"` } func (c *EsiClient) GetLocation() (*LocationData, error) { characterIDStr := os.Getenv("character_id") if characterIDStr == "" { return nil, fmt.Errorf("character_id not set in environment variables") } characterID, _ := strconv.Atoi(characterIDStr) endpoint := fmt.Sprintf("/characters/%d/location/?datasource=tranquility", characterID) resp, err := c.doRequest("GET", endpoint, nil, true) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("failed to get location, status: %d body: %s", resp.StatusCode, string(body)) } var data LocationData if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { return nil, err } return &data, nil } func (c *EsiClient) SetWaypoints(waypoints []int) (*WaypointResult, error) { res := &WaypointResult{ RequestedCount: len(waypoints), Success: true, } for i, wp := range waypoints { clearOther := "false" if i == 0 { clearOther = "true" } endpoint := fmt.Sprintf("/ui/autopilot/waypoint/?add_to_beginning=false&clear_other_waypoints=%s&destination_id=%d&datasource=tranquility", clearOther, wp) resp, err := c.doRequest("POST", endpoint, nil, true) if err != nil { res.FailedWaypoints = append(res.FailedWaypoints, wp) res.Errors = append(res.Errors, err.Error()) res.Success = false continue } if resp.StatusCode >= 200 && resp.StatusCode < 300 { res.SuccessCount++ res.AppliedWaypoints = append(res.AppliedWaypoints, wp) } else { body, _ := io.ReadAll(resp.Body) res.Errors = append(res.Errors, fmt.Sprintf("status %d body %s", resp.StatusCode, string(body))) res.FailedWaypoints = append(res.FailedWaypoints, wp) res.Success = false } resp.Body.Close() time.Sleep(50 * time.Millisecond) // Rate limiting } res.Success = len(res.FailedWaypoints) == 0 return res, nil } func (c *EsiClient) GetWalletTransactions(fromID *int) ([]WalletTransaction, error) { characterIDStr := os.Getenv("character_id") if characterIDStr == "" { return nil, fmt.Errorf("character_id not set in environment variables") } characterID, _ := strconv.Atoi(characterIDStr) endpoint := fmt.Sprintf("/characters/%d/wallet/transactions/?datasource=tranquility", characterID) if fromID != nil { endpoint += fmt.Sprintf("&from_id=%d", *fromID) } resp, err := c.doRequest("GET", endpoint, nil, true) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("failed to get transactions, status: %d body: %s", resp.StatusCode, string(body)) } var baseTx []struct { TransactionID int `json:"transaction_id"` Date string `json:"date"` LocationID int `json:"location_id"` TypeID int `json:"type_id"` UnitPrice float64 `json:"unit_price"` Quantity int `json:"quantity"` ClientID int `json:"client_id"` IsBuy bool `json:"is_buy"` IsPersonal bool `json:"is_personal"` JournalRefID int `json:"journal_ref_id"` } if err := json.NewDecoder(resp.Body).Decode(&baseTx); err != nil { return nil, err } var results []WalletTransaction for _, bt := range baseTx { // ESI dates look like `2026-04-26T01:23:45Z` t, _ := time.Parse(time.RFC3339, bt.Date) results = append(results, WalletTransaction{ TransactionID: bt.TransactionID, Date: t, LocationID: bt.LocationID, TypeID: bt.TypeID, UnitPrice: bt.UnitPrice, Quantity: bt.Quantity, ClientID: bt.ClientID, IsBuy: bt.IsBuy, IsPersonal: bt.IsPersonal, JournalRefID: bt.JournalRefID, }) } return results, nil } func (c *EsiClient) OpenMarketWindow(typeID int) error { endpoint := fmt.Sprintf("/ui/openwindow/marketdetails/?datasource=tranquility&type_id=%d", typeID) resp, err := c.doRequest("POST", endpoint, nil, true) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= 200 && resp.StatusCode < 300 { return nil } body, _ := io.ReadAll(resp.Body) return fmt.Errorf("failed to open market window, status: %d body: %s", resp.StatusCode, string(body)) } func (c *EsiClient) GetRegionOrders(regionID int, page int) ([]map[string]interface{}, string, int, error) { endpoint := fmt.Sprintf("/markets/%d/orders/?datasource=tranquility&order_type=all&page=%d", regionID, page) resp, err := c.doRequest("GET", endpoint, nil, true) if err != nil { return nil, "", 0, err } defer resp.Body.Close() if resp.StatusCode != 200 { body, _ := io.ReadAll(resp.Body) return nil, "", 0, fmt.Errorf("failed to get region orders, status: %d body: %s", resp.StatusCode, string(body)) } var orders []map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&orders); err != nil { return nil, "", 0, err } expires := resp.Header.Get("Expires") totalPagesStr := resp.Header.Get("X-Pages") totalPages, _ := strconv.Atoi(totalPagesStr) if totalPages == 0 { totalPages = 1 } // Add RegionID and drop 0 volume var filtered []map[string]interface{} for _, o := range orders { volRemain, _ := o["volume_remain"].(float64) if volRemain > 0 { o["region_id"] = regionID filtered = append(filtered, o) } } return filtered, expires, totalPages, nil }