Item price support

This commit is contained in:
2026-03-05 22:45:55 -06:00
parent 4282c2a770
commit d920a1e62d
15 changed files with 834 additions and 26 deletions

View File

@@ -122,8 +122,13 @@ func findItemInfobox(templates []extract.Infobox) *extract.Infobox {
}
func renderGEPrice(md *render.Builder, name, pageTitle string) {
if Game() == "rs3" {
renderRS3GEPrice(md, pageTitle)
return
}
priceClient := prices.NewClient(GamePriceBaseURL())
mc := prices.NewMappingCache(priceClient)
mc := prices.NewMappingCache(priceClient, Game())
if err := mc.Load(); err != nil {
return
}
@@ -152,6 +157,32 @@ func renderGEPrice(md *render.Builder, name, pageTitle string) {
md.Newline()
}
func renderRS3GEPrice(md *render.Builder, pageTitle string) {
wikiClient := wiki.NewClient(GameBaseURL())
exchangeItem, err := wikiClient.GetExchangeModule(pageTitle)
if err != nil || exchangeItem == nil {
return
}
rs3Client := prices.NewRS3Client()
detail, err := rs3Client.GetDetail(exchangeItem.ItemID)
if err != nil {
return
}
md.H2("Grand Exchange")
md.KV("Current price", detail.CurrentPrice+" gp")
if detail.TodayPrice != 0 {
md.KV("Today's change", render.FormatGP(detail.TodayPrice))
} else {
md.KV("Today's change", "0 gp")
}
if exchangeItem.Limit > 0 {
md.KV("Buy limit", render.FormatNumber(exchangeItem.Limit))
}
md.Newline()
}
func renderDropSources(md *render.Builder, templates []extract.Infobox) {
drops := extract.FindAllTemplates(templates, "DropsLine")
if len(drops) == 0 {
@@ -182,7 +213,7 @@ func renderSourcesSection(md *render.Builder, page *wiki.ParsedPage, client *wik
lower := strings.ToLower(s.Line)
if lower == "item sources" || lower == "sources" || lower == "obtaining" || lower == "acquisition" {
idx := 0
fmt.Sscanf(s.Index, "%d", &idx)
_, _ = fmt.Sscanf(s.Index, "%d", &idx)
if idx > 0 {
sectionPage, err := client.GetPageSection(title, idx)
if err == nil {

View File

@@ -6,6 +6,7 @@ import (
"github.com/runescape-wiki/rsw/internal/prices"
"github.com/runescape-wiki/rsw/internal/render"
"github.com/runescape-wiki/rsw/internal/wiki"
"github.com/spf13/cobra"
)
@@ -25,8 +26,13 @@ Examples:
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
if Game() == "rs3" {
return runRS3Price(name)
}
priceClient := prices.NewClient(GamePriceBaseURL())
mc := prices.NewMappingCache(priceClient)
mc := prices.NewMappingCache(priceClient, Game())
if err := mc.Load(); err != nil {
return fmt.Errorf("failed to load item mapping: %w", err)
@@ -104,17 +110,16 @@ Examples:
}
hourly, err := priceClient.Get1Hour(item.ID)
if err == nil && len(hourly.Data) > 0 {
recent := hourly.Data[len(hourly.Data)-1]
if err == nil {
md.H2("Recent Activity (1h)")
if recent.AvgHighPrice != nil {
md.KV("Avg buy price", render.FormatGP(*recent.AvgHighPrice))
if hourly.AvgHighPrice != nil {
md.KV("Avg buy price", render.FormatGP(*hourly.AvgHighPrice))
}
md.KV("Buy volume", render.FormatNumber(recent.HighVolume))
if recent.AvgLowPrice != nil {
md.KV("Avg sell price", render.FormatGP(*recent.AvgLowPrice))
md.KV("Buy volume", render.FormatNumber(hourly.HighVolume))
if hourly.AvgLowPrice != nil {
md.KV("Avg sell price", render.FormatGP(*hourly.AvgLowPrice))
}
md.KV("Sell volume", render.FormatNumber(recent.LowVolume))
md.KV("Sell volume", render.FormatNumber(hourly.LowVolume))
md.Newline()
}
}
@@ -125,6 +130,58 @@ Examples:
}
}
func runRS3Price(name string) error {
wikiClient := wiki.NewClient(GameBaseURL())
results, err := wikiClient.Search(name, 5)
if err != nil {
return fmt.Errorf("search failed: %w", err)
}
if len(results) == 0 {
return fmt.Errorf("no wiki page found for %q", name)
}
pageTitle := results[0].Title
exchangeItem, err := wikiClient.GetExchangeModule(pageTitle)
if err != nil {
return fmt.Errorf("failed to look up exchange data: %w", err)
}
if exchangeItem == nil {
return fmt.Errorf("%q is not tradeable on the Grand Exchange", pageTitle)
}
rs3Client := prices.NewRS3Client()
detail, err := rs3Client.GetDetail(exchangeItem.ItemID)
if err != nil {
return fmt.Errorf("failed to fetch RS3 price data: %w", err)
}
md := render.New()
md.H1(detail.Name)
md.KV("Examine", exchangeItem.Examine)
md.KV("Members", fmt.Sprintf("%v", exchangeItem.Members))
if exchangeItem.Limit > 0 {
md.KV("Buy limit", render.FormatNumber(exchangeItem.Limit))
}
md.KV("Store value", render.FormatGP(exchangeItem.Value))
md.Newline()
md.H2("Grand Exchange Price")
md.KV("Current price", detail.CurrentPrice+" gp")
if detail.TodayPrice != 0 {
md.KV("Today's change", render.FormatGP(detail.TodayPrice))
} else {
md.KV("Today's change", "0 gp")
}
md.KV("30-day change", fmt.Sprintf("%s (%s)", detail.Day30Change, detail.Day30Trend))
md.KV("90-day change", fmt.Sprintf("%s (%s)", detail.Day90Change, detail.Day90Trend))
md.KV("180-day change", fmt.Sprintf("%s (%s)", detail.Day180Change, detail.Day180Trend))
md.Newline()
fmt.Print(md.String())
return nil
}
func timeAgo(unixTime int64) string {
t := time.Unix(unixTime, 0)
d := time.Since(t)

View File

@@ -134,7 +134,7 @@ func renderQuestSection(md *render.Builder, page *wiki.ParsedPage, client *wiki.
for _, target := range sectionNames {
if lower == target || strings.Contains(lower, target) {
idx := 0
fmt.Sscanf(s.Index, "%d", &idx)
_, _ = fmt.Sscanf(s.Index, "%d", &idx)
if idx > 0 {
sectionPage, err := client.GetPageSection(title, idx)
if err == nil {

View File

@@ -64,12 +64,11 @@ func GameBaseURL() string {
}
// GamePriceBaseURL returns the real-time price API base URL.
// Only valid for OSRS; RS3 uses a separate client (prices.RS3Client).
func GamePriceBaseURL() string {
switch game {
case "osrs":
return "https://prices.runescape.wiki/api/v1/osrs"
case "rs3":
return "https://prices.runescape.wiki/api/v1/rs"
default:
return ""
}