package wiki import ( "fmt" "net/url" "regexp" "strconv" "strings" ) // RS3ExchangeItem holds metadata parsed from a Module:Exchange/ 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/ 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) }