rssmon3/copart/auction/car.go

252 lines
4.2 KiB
Go

package auction
import (
"bytes"
"encoding/gob"
"encoding/json"
"errors"
"fmt"
"net/url"
"strings"
"time"
)
type Car struct {
Bid string
Details map[string]string
Images []*Image
Title string
Watched bool
TS time.Time
}
type CarOption func(*Car)
func NewCar(options ...CarOption) *Car {
c := &Car{
Details: map[string]string{},
Images: []*Image{},
}
c.parseTS(nil)
for _, opt := range options {
opt(c)
}
return c
}
func (c *Car) MarshalJSON() ([]byte, error) {
m := map[string]interface{}{}
m["bid"] = c.Bid
m["details"] = c.Details
m["images"] = c.Images
m["title"] = c.Title
m["TS"] = c.TS.Format("2006-01-02")
return json.Marshal(m)
}
func (c *Car) Encode() ([]byte, error) {
buff := bytes.NewBuffer(nil)
enc := gob.NewEncoder(buff)
err := enc.Encode(c)
return buff.Bytes(), err
}
func (c *Car) Decode(b []byte) error {
buff := bytes.NewBuffer(b)
enc := gob.NewDecoder(buff)
return enc.Decode(c)
}
func (c *Car) Copy() *Car {
d := *c
return &d
}
func (c *Car) Equals(d *Car) bool {
if c == d {
return true
}
if d == nil && c != nil {
return false
}
if c == nil && d != nil {
return false
}
for k, v := range c.Details {
if w, ok := d.Details[k]; ok && w != v {
return false
}
}
for k, v := range c.Details {
if w, ok := c.Details[k]; ok && w != v {
return false
}
}
if c.Title != d.Title {
return false
}
return true
}
func (c *Car) String() string {
return fmt.Sprintf(
"[bid:%v title:%v watched:%v images:%v when:%v]",
c.Bid,
c.Title,
c.Watched,
len(c.Images),
c.TS,
)
}
func (c *Car) Parse(wd WebDriver) error {
we, err := wd.FindElement("tag name", "body")
if err != nil {
return err
}
do := []func(WebElement) error{
c.parseTitle,
c.parseBid,
c.parseWatched,
c.parseImages,
c.parseDetails,
c.parseTS,
}
for _, d := range do {
if err := d(we); err != nil {
return err
}
}
return nil
}
func (c *Car) parseTS(WebElement) error {
c.TS = time.Now()
return nil
}
func (c *Car) parseBid(wd WebElement) error {
elem := waitForXPath(wd, "//bidding-dialer-refactor")
elem, err := elem.FindElement("tag name", "text")
if err != nil {
return err
}
text, err := elem.Text()
if err != nil {
return err
}
if strings.Contains(text, "$") {
c.Bid = text
}
return nil
}
func (c *Car) parseDetails(wd WebElement) error {
details := make(map[string]string)
elems, err := wd.FindElements("tag name", "perfect-scrollbar")
if err != nil {
return err
}
if len(elems) > 2 {
elems = elems[:2]
}
for _, elem := range elems {
labels, err := elem.FindElements("tag name", "label")
if err != nil {
return err
}
for _, label := range labels {
ltext, err := label.Text()
if err != nil {
return err
}
if ltext == "" {
continue
}
value, err := label.FindElement("xpath", "../div[1]")
if err != nil {
return err
}
vtext, err := value.Text()
if err != nil {
return err
}
details[ltext] = vtext
}
}
if len(details) > 0 {
c.Details = details
}
return nil
}
func (c *Car) parseImages(wd WebElement) error {
elem := waitForXPath(wd, "//ngx-carousel")
elems, err := elem.FindElements("tag name", "ngx-item")
if err != nil {
return err
}
if len(elems) == 0 {
return err
}
images := make([]*Image, 0)
for _, elem := range elems {
elems, err := elem.FindElements("tag name", "img")
if err != nil {
return err
}
for _, img := range elems {
attr, err := img.GetAttribute("src")
if err != nil {
return err
}
url, err := url.Parse(attr)
if err != nil {
return err
}
images = append(images, NewImage(url))
}
}
c.Images = images
return nil
}
func (c *Car) parseTitle(wd WebElement) error {
elem := waitForXPath(wd, "//lot-header")
elem = followTags(elem, "section", "div", "div")
if elem == nil {
return errors.New("not found")
}
text, err := elem.Text()
if err != nil {
return err
}
c.Title = text
return nil
}
func (c *Car) parseWatched(wd WebElement) error {
elem := waitForXPath(wd, "//widget-header-sale")
elems, err := elem.FindElements("tag name", ".//div[@class=\"watchlist\"]")
if err != nil {
return err
}
c.Watched = len(elems) > 0
return nil
}