Item price support
This commit is contained in:
@@ -42,7 +42,7 @@ func (c *Client) get(params url.Values, dest interface{}) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("executing request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
114
scripts/rsw/internal/wiki/exchange.go
Normal file
114
scripts/rsw/internal/wiki/exchange.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package wiki
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RS3ExchangeItem holds metadata parsed from a Module:Exchange/<name> page.
|
||||
type RS3ExchangeItem struct {
|
||||
ItemID int
|
||||
Name string
|
||||
Value int
|
||||
Limit int
|
||||
Members bool
|
||||
Category string
|
||||
Examine string
|
||||
}
|
||||
|
||||
// revisionResponse matches the JSON from action=query&prop=revisions&rvslots=main.
|
||||
type revisionResponse struct {
|
||||
Query struct {
|
||||
Pages map[string]struct {
|
||||
PageID int `json:"pageid"`
|
||||
Title string `json:"title"`
|
||||
Revisions []struct {
|
||||
Slots struct {
|
||||
Main struct {
|
||||
Content string `json:"*"`
|
||||
} `json:"main"`
|
||||
} `json:"slots"`
|
||||
} `json:"revisions"`
|
||||
} `json:"pages"`
|
||||
} `json:"query"`
|
||||
}
|
||||
|
||||
// GetExchangeModule fetches Module:Exchange/<name> and parses item metadata.
|
||||
// Returns nil, nil if the module page does not exist (item not tradeable).
|
||||
func (c *Client) GetExchangeModule(itemName string) (*RS3ExchangeItem, error) {
|
||||
title := "Module:Exchange/" + itemName
|
||||
|
||||
params := url.Values{
|
||||
"action": {"query"},
|
||||
"titles": {title},
|
||||
"prop": {"revisions"},
|
||||
"rvprop": {"content"},
|
||||
"rvslots": {"main"},
|
||||
}
|
||||
|
||||
var resp revisionResponse
|
||||
if err := c.get(params, &resp); err != nil {
|
||||
return nil, fmt.Errorf("fetching exchange module: %w", err)
|
||||
}
|
||||
|
||||
for id, page := range resp.Query.Pages {
|
||||
// Page ID -1 means the page doesn't exist
|
||||
if id == "-1" {
|
||||
return nil, nil
|
||||
}
|
||||
if len(page.Revisions) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
content := page.Revisions[0].Slots.Main.Content
|
||||
return parseLuaModule(content), nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
reItemID = regexp.MustCompile(`itemId\s*=\s*(\d+)`)
|
||||
reItem = regexp.MustCompile(`item\s*=\s*'((?:[^'\\]|\\.)*)'`)
|
||||
reValue = regexp.MustCompile(`value\s*=\s*(\d+)`)
|
||||
reLimit = regexp.MustCompile(`limit\s*=\s*(\d+)`)
|
||||
reMembers = regexp.MustCompile(`members\s*=\s*(true|false)`)
|
||||
reCategory = regexp.MustCompile(`category\s*=\s*'((?:[^'\\]|\\.)*)'`)
|
||||
reExamine = regexp.MustCompile(`examine\s*=\s*'((?:[^'\\]|\\.)*)'`)
|
||||
)
|
||||
|
||||
func parseLuaModule(content string) *RS3ExchangeItem {
|
||||
item := &RS3ExchangeItem{}
|
||||
|
||||
if m := reItemID.FindStringSubmatch(content); m != nil {
|
||||
item.ItemID, _ = strconv.Atoi(m[1])
|
||||
}
|
||||
if m := reItem.FindStringSubmatch(content); m != nil {
|
||||
item.Name = unescapeLua(m[1])
|
||||
}
|
||||
if m := reValue.FindStringSubmatch(content); m != nil {
|
||||
item.Value, _ = strconv.Atoi(m[1])
|
||||
}
|
||||
if m := reLimit.FindStringSubmatch(content); m != nil {
|
||||
item.Limit, _ = strconv.Atoi(m[1])
|
||||
}
|
||||
if m := reMembers.FindStringSubmatch(content); m != nil {
|
||||
item.Members = strings.ToLower(m[1]) == "true"
|
||||
}
|
||||
if m := reCategory.FindStringSubmatch(content); m != nil {
|
||||
item.Category = unescapeLua(m[1])
|
||||
}
|
||||
if m := reExamine.FindStringSubmatch(content); m != nil {
|
||||
item.Examine = unescapeLua(m[1])
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
// unescapeLua handles Lua string escape sequences in single-quoted strings.
|
||||
func unescapeLua(s string) string {
|
||||
return strings.NewReplacer(`\'`, `'`, `\\`, `\`).Replace(s)
|
||||
}
|
||||
108
scripts/rsw/internal/wiki/exchange_integration_test.go
Normal file
108
scripts/rsw/internal/wiki/exchange_integration_test.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package wiki_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// --- RS3 Exchange Module Tests ---
|
||||
|
||||
func TestRS3_GetExchangeModule_AbyssalWhip(t *testing.T) {
|
||||
item, err := rs3Client().GetExchangeModule("Abyssal whip")
|
||||
if err != nil {
|
||||
t.Fatalf("GetExchangeModule failed: %v", err)
|
||||
}
|
||||
if item == nil {
|
||||
t.Fatal("expected non-nil exchange item for Abyssal whip")
|
||||
}
|
||||
|
||||
if item.ItemID != 4151 {
|
||||
t.Errorf("expected item ID 4151, got %d", item.ItemID)
|
||||
}
|
||||
if item.Name != "Abyssal whip" {
|
||||
t.Errorf("expected name 'Abyssal whip', got %q", item.Name)
|
||||
}
|
||||
if !item.Members {
|
||||
t.Error("expected Abyssal whip to be members-only")
|
||||
}
|
||||
if item.Limit <= 0 {
|
||||
t.Errorf("expected positive buy limit, got %d", item.Limit)
|
||||
}
|
||||
if item.Value <= 0 {
|
||||
t.Errorf("expected positive store value, got %d", item.Value)
|
||||
}
|
||||
if item.Examine == "" {
|
||||
t.Error("expected non-empty examine text")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRS3_GetExchangeModule_BluePartyhat(t *testing.T) {
|
||||
item, err := rs3Client().GetExchangeModule("Blue partyhat")
|
||||
if err != nil {
|
||||
t.Fatalf("GetExchangeModule failed: %v", err)
|
||||
}
|
||||
if item == nil {
|
||||
t.Fatal("expected non-nil exchange item for Blue partyhat")
|
||||
}
|
||||
|
||||
if item.ItemID != 1042 {
|
||||
t.Errorf("expected item ID 1042, got %d", item.ItemID)
|
||||
}
|
||||
if item.Members {
|
||||
t.Error("expected Blue partyhat to not be members-only")
|
||||
}
|
||||
if item.Limit <= 0 {
|
||||
t.Errorf("expected positive buy limit, got %d", item.Limit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRS3_GetExchangeModule_DragonBones(t *testing.T) {
|
||||
item, err := rs3Client().GetExchangeModule("Dragon bones")
|
||||
if err != nil {
|
||||
t.Fatalf("GetExchangeModule failed: %v", err)
|
||||
}
|
||||
if item == nil {
|
||||
t.Fatal("expected non-nil exchange item for Dragon bones")
|
||||
}
|
||||
|
||||
if item.ItemID <= 0 {
|
||||
t.Error("expected positive item ID")
|
||||
}
|
||||
if item.Name != "Dragon bones" {
|
||||
t.Errorf("expected name 'Dragon bones', got %q", item.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRS3_GetExchangeModule_NonTradeableItem(t *testing.T) {
|
||||
// "Quest point cape" is not tradeable, so no exchange module exists
|
||||
item, err := rs3Client().GetExchangeModule("Quest point cape")
|
||||
if err != nil {
|
||||
t.Fatalf("GetExchangeModule failed: %v", err)
|
||||
}
|
||||
if item != nil {
|
||||
t.Error("expected nil for non-tradeable item")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRS3_GetExchangeModule_NonExistentItem(t *testing.T) {
|
||||
item, err := rs3Client().GetExchangeModule("Completely Fake Item That Does Not Exist")
|
||||
if err != nil {
|
||||
t.Fatalf("GetExchangeModule failed: %v", err)
|
||||
}
|
||||
if item != nil {
|
||||
t.Error("expected nil for non-existent item")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRS3_GetExchangeModule_HasCategory(t *testing.T) {
|
||||
item, err := rs3Client().GetExchangeModule("Abyssal whip")
|
||||
if err != nil {
|
||||
t.Fatalf("GetExchangeModule failed: %v", err)
|
||||
}
|
||||
if item == nil {
|
||||
t.Fatal("expected non-nil exchange item")
|
||||
}
|
||||
|
||||
if item.Category == "" {
|
||||
t.Error("expected non-empty category")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user