Last active
January 24, 2022 02:07
-
-
Save rms1000watt/c22cab5aed126824ac0c680fce6669aa to your computer and use it in GitHub Desktop.
Golang script for concurrent Nintendo Switch quantity checks against Walmart and Target
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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