Skip to content

Instantly share code, notes, and snippets.

@rms1000watt
Last active January 24, 2022 02:07
Show Gist options
  • Select an option

  • Save rms1000watt/c22cab5aed126824ac0c680fce6669aa to your computer and use it in GitHub Desktop.

Select an option

Save rms1000watt/c22cab5aed126824ac0c680fce6669aa to your computer and use it in GitHub Desktop.
Golang script for concurrent Nintendo Switch quantity checks against Walmart and Target
package main
import (
"compress/gzip"
"fmt"
"io/ioutil"
"net/http"
"time"
"encoding/json"
"strings"
)
var (
URLTarget = "https://api.target.com/available_to_promise/v2/52052007/search?key=eb2551e4accc14f38cc42d32fbc2b2ea&nearby=*ZIPCODE*&inventory_type=stores&multichannel_option=none&field_groups=location_summary&requested_quantity=1&radius=100"
URLWalmart = "https://www.walmart.com/terra-firma/item/3B092LFMR8PF/location/*ZIPCODE*?selected=true&wl13="
ZipCode = "92626"
OutFile = "./switch-availability.json"
)
type Config struct {
ZipCode string
}
type Report struct {
Time string
ZipCode string
Companies []Company
}
type Company struct {
Name string
Stores []Store
}
type Store struct {
Name string
Location string
Quantity int
Availability string
}
type TargetResponse struct {
Products []TargetProduct `json:"products,omitempty"`
}
type TargetProduct struct {
Locations []TargetLocation `json:"locations,omitempty"`
}
type TargetLocation struct {
AvailabilityStatus string `json:"availability_status,omitempty"`
StoreName string `json:"store_name,omitempty"`
StoreAddress string `json:"store_address,omitempty"`
OnhandQuantity float64 `json:"onhand_quantity,omitempty"`
}
type WalmartResponse struct {
Payload WalmartPayload `json:"payload,omitempty"`
}
type WalmartPayload struct {
Offers map[string]WalmartOffer `json:"offers,omitempty"`
}
type WalmartOffer struct {
Fulfillment WalmartFulfillment `json:"fulfillment,omitempty"`
}
type WalmartFulfillment struct {
PickupOptions []WalmartPickupOption `json:"pickupOptions,omitempty"`
}
type WalmartPickupOption struct {
Availability string `json:"availability,omitempty"`
StoreAddress string `json:"storeAddress,omitempty"`
StoreCity string `json:"storeCity,omitempty"`
StoreName string `json:"storeName,omitempty"`
}
func main() {
cfg := getConfig()
if err := genReport(cfg); err != nil {
fmt.Println("Gen Report Error:", err)
}
}
func getConfig() Config {
return Config{
ZipCode: ZipCode,
}
}
func genReport(cfg Config) error {
report := Report{
Time: time.Now().Format(time.RFC3339Nano),
ZipCode: cfg.ZipCode,
}
totalCnt := 2
companyCh := make(chan Company, totalCnt)
go func(ch chan Company, cfg Config) {
target, err := getTarget(cfg)
if err != nil {
fmt.Println("Failed getting Target")
} else {
fmt.Println("Got Target")
}
ch <- target
}(companyCh, cfg)
go func(ch chan Company, cfg Config) {
walmart, err := getWalmart(cfg)
if err != nil {
fmt.Println("Failed getting Walmart")
} else {
fmt.Println("Got Walmart")
}
ch <- walmart
}(companyCh, cfg)
currentCnt := 0
for {
select {
case company := <-companyCh:
report.Companies = append(report.Companies, company)
currentCnt++
case <-time.After(time.Second * 3):
fmt.Println("timeout after 3 seconds")
currentCnt = totalCnt
break
}
if totalCnt == currentCnt {
break
}
}
jsonBytes, err := json.MarshalIndent(report, "", " ")
if err != nil {
fmt.Println("Report marshall error:", err)
return err
}
if err := ioutil.WriteFile(OutFile, jsonBytes, 0644); err != nil {
fmt.Println("Write File error:", err)
return err
}
fmt.Println("Wrote to:", OutFile)
return nil
}
func getTarget(cfg Config) (Company, error) {
target := Company{
Name: "Target",
}
urlTarget := strings.Replace(URLTarget, "*ZIPCODE*", cfg.ZipCode, -1)
req, err := http.NewRequest("GET", urlTarget, nil)
req.Header.Add("Accept", "application/json, text/javascript, */*; q=0.01")
req.Header.Add("Accept-Encoding", "gzip, deflate, sdch, br")
req.Header.Add("Accept-Language", "en-US,en;q=0.8")
req.Header.Add("Connection", "keep-alive")
req.Header.Add("Host", "api.target.com")
req.Header.Add("Origin", "http://www.target.com")
req.Header.Add("Referer", "http://www.target.com/p/nintendo-switch-with-gray-joy-con/-/A-52052007?lnk=fiatsCookie")
req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36")
responseBytes, err := getResponseBytes(req)
if err != nil {
fmt.Println("Target Error 1:", err)
return target, err
}
targetResponse := TargetResponse{}
if err := json.Unmarshal(responseBytes, &targetResponse); err != nil {
fmt.Println("Target unmarshall error:", err)
return target, err
}
stores := []Store{}
for _, product := range targetResponse.Products {
for _, location := range product.Locations {
stores = append(stores, Store{
Name: location.StoreName,
Location: location.StoreAddress,
Availability: location.AvailabilityStatus,
Quantity: int(location.OnhandQuantity),
})
}
}
target.Stores = stores
return target, nil
}
func getWalmart(cfg Config) (Company, error) {
walmart := Company{
Name: "Walmart",
}
urlWalmart := strings.Replace(URLWalmart, "*ZIPCODE*", cfg.ZipCode, -1)
req, err := http.NewRequest("GET", urlWalmart, nil)
req.Header.Add("Accept", "*/*")
req.Header.Add("Accept-Encoding", "gzip, deflate, sdch, br")
req.Header.Add("Accept-Language", "en-US,en;q=0.8")
req.Header.Add("Authority", "www.walmart.com")
req.Header.Add("Referer", "https://www.walmart.com/ip/Nintendo-Switch-Gaming-Console-with-Gray-Joy-Con-N-A/55449983")
req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36")
responseBytes, err := getResponseBytes(req)
if err != nil {
fmt.Println("Walmart Error 1:", err)
return walmart, err
}
walmartResponse := WalmartResponse{}
if err := json.Unmarshal(responseBytes, &walmartResponse); err != nil {
fmt.Println("Walmart Unmarshall Error:", err)
return walmart, err
}
stores := []Store{}
for _, offer := range walmartResponse.Payload.Offers {
for _, pickupOption := range offer.Fulfillment.PickupOptions {
stores = append(stores, Store{
Name: pickupOption.StoreName,
Location: fmt.Sprintf("%s, %s", pickupOption.StoreAddress, pickupOption.StoreCity),
Availability: pickupOption.Availability,
Quantity: -1,
})
}
}
walmart.Stores = stores
return walmart, nil
}
func getResponseBytes(req *http.Request) ([]byte, error) {
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.Header.Get("Content-Encoding") == "gzip" {
resp.Body, err = gzip.NewReader(resp.Body)
if err != nil {
return nil, err
}
}
responseBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return responseBytes, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment