truckstop/broker/ntgvision.go

207 lines
5.7 KiB
Go

package broker
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"local/truckstop/config"
"net/http"
"time"
)
type NTGVision struct {
searcher interface {
search(states []config.State) (io.ReadCloser, error)
}
}
var ErrNoAuth = errors.New("not authorized")
type ntgVisionJob struct {
ID int64 `json:"id"`
PickupDate string `json:"sDate"`
PickupCity string `json:"sCity"`
PickupState string `json:"sState"`
DropoffDate string `json:"cDate"`
DropoffCity string `json:"cCity"`
DropoffState string `json:"cState"`
Miles int `json:"miles"`
Weight int `json:"weight"`
Equipment string `json:"equip"`
Temp string `json:"temp"`
}
func (ntgJob ntgVisionJob) Job() Job {
pickup, _ := time.ParseInLocation("01/02/06", ntgJob.PickupDate, time.Local)
dropoff, _ := time.ParseInLocation("01/02/06", ntgJob.DropoffDate, time.Local)
return Job{
ID: fmt.Sprintf("ntg-%d", ntgJob.ID),
Pickup: JobLocation{
Date: pickup,
City: ntgJob.PickupCity,
State: ntgJob.PickupState,
},
Dropoff: JobLocation{
Date: dropoff,
City: ntgJob.DropoffCity,
State: ntgJob.DropoffState,
},
Miles: ntgJob.Miles,
Weight: ntgJob.Weight,
Meta: fmt.Sprintf("equipment:%s", ntgJob.Equipment),
}
}
func NewNTGVision() NTGVision {
ntgv := NTGVision{}
ntgv.searcher = ntgv
return ntgv
}
func (ntg NTGVision) WithMock() NTGVision {
ntg.searcher = NewNTGVisionMock()
return ntg
}
func (ntg NTGVision) Search(states []config.State) ([]Job, error) {
rc, err := ntg.searcher.search(states)
if err != nil {
return nil, err
}
defer rc.Close()
var ntgjobs []ntgVisionJob
err = json.NewDecoder(rc).Decode(&ntgjobs)
jobs := make([]Job, len(ntgjobs))
for i := range jobs {
jobs[i] = ntgjobs[i].Job()
}
return jobs, err
}
func (ntg NTGVision) search(states []config.State) (io.ReadCloser, error) {
if config.Get().Brokers.NTG.Token == "" {
if err := ntg.refreshAuth(); err != nil {
return nil, err
}
}
rc, err := ntg._search(states)
if err == ErrNoAuth {
if err := ntg.refreshAuth(); err != nil {
return nil, err
}
rc, err = ntg._search(states)
}
return rc, err
}
func (ntg NTGVision) refreshAuth() error {
b, _ := json.Marshal(map[string]string{
"username": config.Get().Brokers.NTG.Username,
"password": config.Get().Brokers.NTG.Password,
})
request, err := http.NewRequest(http.MethodPost, "https://ntgvision.com/api/v1/sts/Login", bytes.NewReader(b))
if err != nil {
return err
}
setNTGHeaders(request)
authlimiter.Wait(context.Background())
resp, err := http.DefaultClient.Do(request)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
b, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("failed refreshing token: (%d): %s", resp.StatusCode, b)
}
var v struct {
Token string `json:"token"`
}
if err := json.NewDecoder(resp.Body).Decode(&v); err != nil {
return err
}
if len(v.Token) == 0 {
return errors.New("failed to get token from login call")
}
conf := config.Get()
conf.Brokers.NTG.Token = v.Token
config.Set(*conf)
return nil
}
func (ntg NTGVision) _search(states []config.State) (io.ReadCloser, error) {
request, err := ntg.newRequest(states)
if err != nil {
return nil, err
}
c := http.Client{Timeout: time.Minute}
limiter.Wait(context.Backgroud())
resp, err := c.Do(request)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
b, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode > 400 && resp.StatusCode < 500 && resp.StatusCode != 404 && resp.StatusCode != 410 {
return nil, ErrNoAuth
}
return nil, fmt.Errorf("bad status searching ntg: %d: %s", resp.StatusCode, b)
}
return resp.Body, nil
}
func (ntg NTGVision) newRequest(states []config.State) (*http.Request, error) {
body, err := json.Marshal(map[string]interface{}{
"OriginFromDate": time.Now().UTC().Format("2006-01-02T15:04:05.000Z"),
"OriginToDate": time.Now().UTC().Add(time.Hour * 24 * 30).Format("2006-01-02T15:04:05.000Z"),
"DestinationFromDate": nil,
"DestinationToDate": nil,
"OriginState": states,
"OriginRadius": 100,
"DestinationState": []string{},
"DestinationRadius": 100,
"EquipmentTypes": []string{"STRAIGHT TRUCK", "str truck w/ lift gate"},
"MaxMilesLimit": nil,
"MaxNumberOfStopsLimit": nil,
"MaxWeightLimit": nil,
"MinMilesLimit": nil,
"SavedSearchId": 2956,
})
if err != nil {
return nil, err
}
request, err := http.NewRequest(http.MethodPost, "https://ntgvision.com/api/v1/load/LoadBoardSearchResults", bytes.NewReader(body))
if err != nil {
return nil, err
}
setNTGHeaders(request)
request.Header.Set("Authorization", "Bearer "+config.Get().Brokers.NTG.Token)
return request, nil
}
func setNTGHeaders(request *http.Request) {
request.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0")
request.Header.Set("Accept", "application/json, text/plain, */*")
request.Header.Set("Accept-Language", "en-US,en;q=0.5")
request.Header.Set("Accept-Encoding", "gzip, deflate, br")
request.Header.Set("Content-Type", "application/json;charset=utf-8")
//request.Header.Set("Authorization", "Bearer "+config.Get().Brokers.NTG.Token)
request.Header.Set("Origin", "https://ntgvision.com")
request.Header.Set("DNT", "1")
request.Header.Set("Connection", "keep-alive")
//request.Header.Set("Cookie", config.Get().Brokers.NTG.Cookie)
request.Header.Set("Sec-Fetch-Dest", "empty")
request.Header.Set("Sec-Fetch-Mode", "cors")
request.Header.Set("Sec-Fetch-Site", "same-origin")
request.Header.Set("Pragma", "no-cache")
request.Header.Set("Cache-Control", "no-cache")
}