package solar import ( "encoding/xml" "fmt" "io" "net/http" "strings" "time" ) // SolarData contains current solar and geomagnetic conditions type SolarData struct { SolarFluxIndex int `json:"sfi"` // Solar Flux Index Sunspots int `json:"sunspots"` // Number of sunspots AIndex int `json:"a_index"` // A-index (geomagnetic activity) KIndex int `json:"k_index"` // K-index (geomagnetic activity) GeomagField string `json:"geomag"` // Geomagnetic field status UpdatedAt string `json:"updated"` // Last update time } // HamQSLResponse matches the XML structure from hamqsl.com type HamQSLResponse struct { XMLName xml.Name `xml:"solar"` SolarData SolarDataXML `xml:"solardata"` } type SolarDataXML struct { Updated string `xml:"updated"` SolarFlux string `xml:"solarflux"` AIndex string `xml:"aindex"` KIndex string `xml:"kindex"` Sunspots string `xml:"sunspots"` GeomagField string `xml:"geomagfield"` } type Client struct { httpClient *http.Client lastUpdate time.Time cachedData *SolarData } func New() *Client { return &Client{ httpClient: &http.Client{ Timeout: 10 * time.Second, }, } } // GetSolarData fetches current solar data from HamQSL // Data is cached for 15 minutes to avoid excessive requests func (c *Client) GetSolarData() (*SolarData, error) { // Return cached data if less than 15 minutes old if c.cachedData != nil && time.Since(c.lastUpdate) < 15*time.Minute { return c.cachedData, nil } resp, err := c.httpClient.Get("http://www.hamqsl.com/solarxml.php") if err != nil { return nil, fmt.Errorf("failed to fetch solar data: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } var hamqslData HamQSLResponse if err := xml.Unmarshal(body, &hamqslData); err != nil { return nil, fmt.Errorf("failed to parse XML: %w", err) } // Parse the data solarData := &SolarData{ GeomagField: hamqslData.SolarData.GeomagField, UpdatedAt: hamqslData.SolarData.Updated, } // Parse numeric values (trim spaces) fmt.Sscanf(strings.TrimSpace(hamqslData.SolarData.SolarFlux), "%d", &solarData.SolarFluxIndex) fmt.Sscanf(strings.TrimSpace(hamqslData.SolarData.AIndex), "%d", &solarData.AIndex) fmt.Sscanf(strings.TrimSpace(hamqslData.SolarData.KIndex), "%d", &solarData.KIndex) fmt.Sscanf(strings.TrimSpace(hamqslData.SolarData.Sunspots), "%d", &solarData.Sunspots) // Cache the data c.cachedData = solarData c.lastUpdate = time.Now() return solarData, nil }