Item price support
This commit is contained in:
125
scripts/rsw/internal/prices/rs3client.go
Normal file
125
scripts/rsw/internal/prices/rs3client.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package prices
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const rs3BaseURL = "https://secure.runescape.com/m=itemdb_rs"
|
||||
|
||||
// RS3Client wraps HTTP requests to the Jagex RS3 Grand Exchange API.
|
||||
type RS3Client struct {
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewRS3Client creates an RS3 price API client.
|
||||
func NewRS3Client() *RS3Client {
|
||||
return &RS3Client{
|
||||
httpClient: &http.Client{
|
||||
Timeout: 15 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// RS3ItemDetail holds current price and trend data for an RS3 item.
|
||||
type RS3ItemDetail struct {
|
||||
ID int
|
||||
Name string
|
||||
Description string
|
||||
Members string
|
||||
CurrentPrice string // pre-formatted, e.g. "91.6b"
|
||||
CurrentTrend string
|
||||
TodayPrice int
|
||||
TodayTrend string
|
||||
Day30Change string
|
||||
Day30Trend string
|
||||
Day90Change string
|
||||
Day90Trend string
|
||||
Day180Change string
|
||||
Day180Trend string
|
||||
}
|
||||
|
||||
// rs3DetailResponse matches the JSON shape from the Jagex catalogue API.
|
||||
type rs3DetailResponse struct {
|
||||
Item struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type"`
|
||||
Members string `json:"members"`
|
||||
Current struct {
|
||||
Trend string `json:"trend"`
|
||||
Price any `json:"price"` // string or int
|
||||
} `json:"current"`
|
||||
Today struct {
|
||||
Trend string `json:"trend"`
|
||||
Price any `json:"price"` // int or string
|
||||
} `json:"today"`
|
||||
Day30 struct {
|
||||
Trend string `json:"trend"`
|
||||
Change string `json:"change"`
|
||||
} `json:"day30"`
|
||||
Day90 struct {
|
||||
Trend string `json:"trend"`
|
||||
Change string `json:"change"`
|
||||
} `json:"day90"`
|
||||
Day180 struct {
|
||||
Trend string `json:"trend"`
|
||||
Change string `json:"change"`
|
||||
} `json:"day180"`
|
||||
} `json:"item"`
|
||||
}
|
||||
|
||||
// GetDetail fetches current price and trend data for an RS3 item by ID.
|
||||
func (c *RS3Client) GetDetail(itemID int) (*RS3ItemDetail, error) {
|
||||
url := fmt.Sprintf("%s/api/catalogue/detail.json?item=%d", rs3BaseURL, itemID)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("executing request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close() //nolint:errcheck
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("RS3 API returned status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var raw rs3DetailResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&raw); err != nil {
|
||||
return nil, fmt.Errorf("decoding response: %w", err)
|
||||
}
|
||||
|
||||
detail := &RS3ItemDetail{
|
||||
ID: raw.Item.ID,
|
||||
Name: raw.Item.Name,
|
||||
Description: raw.Item.Description,
|
||||
Members: raw.Item.Members,
|
||||
CurrentPrice: fmt.Sprintf("%v", raw.Item.Current.Price),
|
||||
CurrentTrend: raw.Item.Current.Trend,
|
||||
TodayTrend: raw.Item.Today.Trend,
|
||||
Day30Change: raw.Item.Day30.Change,
|
||||
Day30Trend: raw.Item.Day30.Trend,
|
||||
Day90Change: raw.Item.Day90.Change,
|
||||
Day90Trend: raw.Item.Day90.Trend,
|
||||
Day180Change: raw.Item.Day180.Change,
|
||||
Day180Trend: raw.Item.Day180.Trend,
|
||||
}
|
||||
|
||||
// today.price can be int or string
|
||||
switch v := raw.Item.Today.Price.(type) {
|
||||
case float64:
|
||||
detail.TodayPrice = int(v)
|
||||
}
|
||||
|
||||
return detail, nil
|
||||
}
|
||||
Reference in New Issue
Block a user