fuzzyworld

find-stone-beer / files / main.go

main.go

Blame: the commit that last changed each line · file · history

352a2a02 2023-08-22 Pavel Safronov1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "io"
7 "net/http"
8 "os"
9 "path/filepath"
10 "strings"
11 "time"
12
13 "github.com/jftuga/geodist"
4ee45a45 2023-08-23 Pavel Safronov14 "golang.org/x/exp/slices"
352a2a02 2023-08-22 Pavel Safronov15)
16
4ee45a45 2023-08-23 Pavel Safronov17type relevantSeller struct {
18 Name string
19 Pos geodist.Coord
20 DeliveryAgeDays int
cc4e48df 2023-08-23 Pavel Safronov21 Message string
4ee45a45 2023-08-23 Pavel Safronov22}
23
74d9b692 2023-08-23 Pavel Safronov24const (
25 COORDINATE_LAT_ENV_VAR = "COORDINATE_LAT"
26 COORDINATE_LON_ENV_VAR = "COORDINATE_LON"
27 RANGE_MILES_ENV_VAR = "RANGE_MILES"
28 MAX_DELIVERY_DAYS_ENV_VAR = "MAX_DELIVERY_DAYS"
29)
352a2a02 2023-08-22 Pavel Safronov30
31func main() {
74d9b692 2023-08-23 Pavel Safronov32 // coords for search area
8da39f4a 2023-08-27 Pavel Safronov33 coords, err := GetCoordsFromEnvVars()
352a2a02 2023-08-22 Pavel Safronov34 if err != nil {
35 panic(err)
36 }
37
74d9b692 2023-08-23 Pavel Safronov38 // load other settings from env vars
8da39f4a 2023-08-27 Pavel Safronov39 var rangeMiles = GetEnvVarInteger(RANGE_MILES_ENV_VAR, 15)
40 var maxDeliveryWindowDays = GetEnvVarInteger(MAX_DELIVERY_DAYS_ENV_VAR, 14)
74d9b692 2023-08-23 Pavel Safronov41
42 // descriptions of the beers and packaging
59f69900 2023-08-27 Pavel Safronov43 beersInfo, err := GetBeersInfo()
74d9b692 2023-08-23 Pavel Safronov44 if err != nil {
45 panic(err)
46 }
47
48 // get sellers by location
59f69900 2023-08-27 Pavel Safronov49 sellers, err := GetSellers(coords)
352a2a02 2023-08-22 Pavel Safronov50 if err != nil {
51 panic(err)
52 }
53
54 // filter sellers that sell arrogant bastard
55 arrogantBastardSellers := []Seller{}
56 for _, seller := range sellers {
57 for _, brandId := range seller.AllBrands {
74d9b692 2023-08-23 Pavel Safronov58 foundBrand, brand := beersInfo.FindBrandById(brandId)
352a2a02 2023-08-22 Pavel Safronov59 if foundBrand {
60 if strings.Contains(strings.ToLower(brand.Name), "arrogant") {
61 arrogantBastardSellers = append(arrogantBastardSellers, seller)
62 }
63 } else {
64 panic("Could not find brand")
65 }
66 }
67 }
68
69 now := time.Now()
4ee45a45 2023-08-23 Pavel Safronov70 relevantSellers := []relevantSeller{}
71
352a2a02 2023-08-22 Pavel Safronov72 for _, seller := range arrogantBastardSellers {
577ed62e 2023-08-27 Pavel Safronov73 // quick distance check, gives us the crowd-sourced distance, which is the shortest distance between two points, so using this as a range check is a good and cheap first pass
352a2a02 2023-08-22 Pavel Safronov74 dist, _, err := geodist.VincentyDistance(coords, seller.Pos)
75 if err != nil {
76 panic(err)
77 }
78 if dist > float64(rangeMiles) {
79 continue
80 }
81
577ed62e 2023-08-27 Pavel Safronov82 // calculate route distance, which is the actual distance you would drive, using Bing Maps API
83 // the results are cached for the month, for each set of coordinates, so this is a stable cached operation
84 dist, err = GetDistBetweenPoints(coords, seller.Pos)
85 if err != nil {
86 panic(err)
87 }
88 if dist > float64(rangeMiles) {
89 continue
90 }
91
59f69900 2023-08-27 Pavel Safronov92 sellerInfo, err := GetSellerInfo(seller.Nid)
352a2a02 2023-08-22 Pavel Safronov93 if err != nil {
94 panic(err)
95 }
96 for _, beer := range sellerInfo.Beers {
74d9b692 2023-08-23 Pavel Safronov97 ok, brand := beersInfo.FindBrandById(beer.Brand)
352a2a02 2023-08-22 Pavel Safronov98 if !ok {
99 panic("Could not find brand")
100 }
101 if !strings.Contains(strings.ToLower(brand.Name), "arrogant") {
102 continue
103 }
104 lastDeliveredDuration := now.Sub(beer.Delivered)
105 daysSinceDelivery := int(lastDeliveredDuration.Hours() / 24)
106 if daysSinceDelivery > maxDeliveryWindowDays {
107 continue
108 }
cc4e48df 2023-08-23 Pavel Safronov109 mapsLink := getGoogleMapsLink(seller.Pos)
aba75e09 2023-08-27 Pavel Safronov110 replacement, hasReplacement := NameReplacements[seller.Title]
111 if hasReplacement {
112 seller.Title = replacement
113 }
5b52b062 2023-08-27 Pavel Safronov114 message := fmt.Sprintf("<a href='%s'>%s (%.1fmi)</a> - %dd\n", mapsLink, seller.Title, dist, daysSinceDelivery)
cc4e48df 2023-08-23 Pavel Safronov115
4ee45a45 2023-08-23 Pavel Safronov116 relevantSellers = append(relevantSellers, relevantSeller{
117 Name: seller.Title,
118 Pos: seller.Pos,
119 DeliveryAgeDays: daysSinceDelivery,
cc4e48df 2023-08-23 Pavel Safronov120 Message: message,
4ee45a45 2023-08-23 Pavel Safronov121 })
352a2a02 2023-08-22 Pavel Safronov122 }
4ee45a45 2023-08-23 Pavel Safronov123 }
124
125 // sort by delivery age
126 slices.SortFunc(relevantSellers, func(a, b relevantSeller) int {
127 return a.DeliveryAgeDays - b.DeliveryAgeDays
128 })
129
abecb80c 2023-08-27 Pavel Safronov130 var moreInfoText = "\n<a href='https://pavelsafronov.gitlab.io/find-stone-beer/'>...continue</a>"
131 var startingText = fmt.Sprintf("Sellers on %d/%d/%d\n", now.Year(), now.Month(), now.Day())
132 var lines []string
4ee45a45 2023-08-23 Pavel Safronov133 for _, seller := range relevantSellers {
abecb80c 2023-08-27 Pavel Safronov134 lines = append(lines, seller.Message)
352a2a02 2023-08-22 Pavel Safronov135 }
abecb80c 2023-08-27 Pavel Safronov136 var message = CombineUpToMaxMessageLength(startingText, moreInfoText, lines)
4ee45a45 2023-08-23 Pavel Safronov137 fmt.Printf("Message length: %d\n", len(message))
ab57618f 2023-08-23 Pavel Safronov138 SendPushoverMessage(message)
cc4e48df 2023-08-23 Pavel Safronov139
c9c0c46a 2023-08-23 Pavel Safronov140 relevantSellersJson, err := json.MarshalIndent(relevantSellers, "", " ")
141 if err != nil {
142 panic(err)
e9238860 2023-08-23 Pavel Safronov143 }
c9c0c46a 2023-08-23 Pavel Safronov144 resultsJson := fmt.Sprintf("var results = %s;", relevantSellersJson)
145 outPath := "./public/results.js"
146 err = os.WriteFile(outPath, []byte(resultsJson), 0644)
cc4e48df 2023-08-23 Pavel Safronov147 if err != nil {
148 panic(err)
149 }
4ee45a45 2023-08-23 Pavel Safronov150}
352a2a02 2023-08-22 Pavel Safronov151
4ee45a45 2023-08-23 Pavel Safronov152func getGoogleMapsLink(coords geodist.Coord) string {
153 // https://www.google.com/maps/search/?api=1&query=<lat>,<lng>
154 // https://www.google.com/maps/search/?api=1&query=47.665824,-122.197289
155 return fmt.Sprintf("https://www.google.com/maps/search/?api=1&query=%f,%f", coords.Lat, coords.Lon)
352a2a02 2023-08-22 Pavel Safronov156}
157
158func getCachedUrl(url string, id string, cacheFileTimeFormat string) ([]byte, error) {
159 return getCachedUrlFunc(url, id, cacheFileTimeFormat, func(bytes []byte) ([]byte, error) {
160 return bytes, nil
161 })
162}
163func getCachedUrlFunc(url string, id string, cacheFileTimeFormat string, byteProcessor func([]byte) ([]byte, error)) ([]byte, error) {
164 now := time.Now()
165 nowFormatted := now.Format(cacheFileTimeFormat)
166 cacheFile := fmt.Sprintf("cache/%s-%s.txt", id, nowFormatted)
167 dir := filepath.Dir(cacheFile)
168 _, err := os.Stat(dir)
169 dirExists := err == nil || !os.IsNotExist(err)
170 if !dirExists {
171 err = os.Mkdir(dir, 0755)
172 if err != nil {
173 return nil, err
174 }
175 }
176
177 _, err = os.Stat(cacheFile)
178 fileExists := err == nil || !os.IsNotExist(err)
179 if fileExists {
180 f, err := os.Open(cacheFile)
181 if err != nil {
182 return nil, err
183 }
184 defer f.Close()
185 bytes, err := io.ReadAll(f)
186 if err != nil {
187 return nil, err
188 }
189 return bytes, nil
190 } else {
191 resp, err := http.Get(url)
192 if err != nil {
193 return nil, err
194 }
195 defer resp.Body.Close()
196
197 body, err := io.ReadAll(resp.Body)
198 if err != nil {
199 return nil, err
200 }
201
4cc684dc 2023-09-14 Pavel Safronov202 if resp.StatusCode != 200 {
203 return nil, fmt.Errorf("tried reaching %s, got status code %d and body %s", url, resp.StatusCode, string(body))
204 }
205
352a2a02 2023-08-22 Pavel Safronov206 body, err = byteProcessor(body)
207 if err != nil {
208 return nil, err
209 }
210
211 f, err := os.Create(cacheFile)
212 if err != nil {
213 return nil, err
214 }
215 defer f.Close()
216 _, err = f.Write(body)
217 if err != nil {
218 return nil, err
219 }
220
221 return body, nil
222 }
223}