252 lines
4.2 KiB
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
|
|
}
|