129 lines
3.0 KiB
Go
129 lines
3.0 KiB
Go
package reichelt
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/andybalholm/cascadia"
|
|
|
|
"golang.org/x/net/html"
|
|
)
|
|
|
|
type Part struct {
|
|
Number int
|
|
Description string
|
|
}
|
|
|
|
// used for the json Decoder
|
|
// The fields are then copied to Part
|
|
// (This was done to remove the structure tags).
|
|
type partInternal struct {
|
|
Number int `json:"article_artid"`
|
|
Description string `json:"article_lang_besch"`
|
|
}
|
|
|
|
// Where to dispatch api queries to
|
|
const apiurl = "https://www.reichelt.de/index.html"
|
|
|
|
// One Response-field from the autocomplete
|
|
// endpoint (a small excerpt from it)
|
|
type responseField struct {
|
|
NumFound int `json:"numFound"`
|
|
MaxScore float32 `json:"maxScore"`
|
|
|
|
Docs []partInternal `json:"docs"`
|
|
}
|
|
|
|
// The Full response
|
|
// notice: There are more fields that the server response contains
|
|
type searchResponse struct {
|
|
Response responseField `json:"response"`
|
|
}
|
|
|
|
var priceSelector = cascadia.MustCompile("#av_price")
|
|
|
|
// Search for a part like using the sites search engine
|
|
// can be used to resolv partnumbers to internal ones
|
|
func (c *Connection) FindPart(query string) (result []Part, err error) {
|
|
resp, err := c.client.Get(apiurl + "?ACTION=514&id=8&term=" + url.PathEscape(query))
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("wrong response status: %d", resp.StatusCode)
|
|
}
|
|
|
|
reader := json.NewDecoder(resp.Body)
|
|
response := searchResponse{}
|
|
if err = reader.Decode(&response); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result = make([]Part, len(response.Response.Docs))
|
|
|
|
for i, j := range response.Response.Docs {
|
|
result[i] = Part{
|
|
Number: j.Number,
|
|
Description: j.Description,
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Returns the Price of the Part
|
|
// or 0 if there was an error
|
|
func (c *Connection) GetPrice(p Part) float32 {
|
|
resp, err := c.client.Get(apiurl + "?ACTION=3&ARTICLE=" + strconv.Itoa(p.Number))
|
|
|
|
if err != nil {
|
|
// log.Println("price:", "get request:", err)
|
|
return 0
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
// log.Println("price:", "wrong result:", resp.Status)
|
|
return 0
|
|
}
|
|
|
|
doc, err := html.Parse(resp.Body)
|
|
if err != nil {
|
|
// log.Println("price:", "parse html:", err)
|
|
return 0
|
|
}
|
|
priceTag := priceSelector.MatchFirst(doc)
|
|
|
|
if priceTag == nil {
|
|
// log.Println("price:", "selector returned nothing")
|
|
return 0
|
|
}
|
|
|
|
// retrieve first child of node
|
|
// since inner Text is saved as child node
|
|
// NOTE: This might be the wrong node, but if it is,
|
|
// code below this one will fail anyway, so we dont check
|
|
// the node type here
|
|
price := priceTag.FirstChild.Data
|
|
|
|
// split before € sign
|
|
i := strings.Index(price, " €")
|
|
if i == len(price) || len(price) == 0 {
|
|
return 0
|
|
}
|
|
|
|
// need to convert german float (using ,) to american decimals
|
|
// using . for ParseFloat (since ParseFloat is not localized)
|
|
str := strings.Replace(price[:i], ",", ".", 1)
|
|
ret, _ := strconv.ParseFloat(str, 32)
|
|
return float32(ret)
|
|
}
|