find-stone-beer / files / beers-info.go
beers-info.go
352a2a02 2023-08-22 Pavel Safronov | 1 | package main |
| 2 | ||
| 3 | import ( | |
59f69900 2023-08-27 Pavel Safronov | 4 | "bytes" |
352a2a02 2023-08-22 Pavel Safronov | 5 | "encoding/json" |
59f69900 2023-08-27 Pavel Safronov | 6 | "errors" |
| 7 | "strings" | |
| 8 | ||
| 9 | "golang.org/x/net/html" | |
352a2a02 2023-08-22 Pavel Safronov | 10 | ) |
| 11 | ||
| 12 | type Core struct { | |
74d9b692 2023-08-23 Pavel Safronov | 13 | Beers BeersInfo |
352a2a02 2023-08-22 Pavel Safronov | 14 | } |
| 15 | ||
| 16 | func (c *Core) UnmarshalJSON(data []byte) error { | |
74d9b692 2023-08-23 Pavel Safronov | 17 | var rawMap map[string]json.RawMessage |
| 18 | err := json.Unmarshal(data, &rawMap) | |
352a2a02 2023-08-22 Pavel Safronov | 19 | if err != nil { |
| 20 | return err | |
| 21 | } | |
74d9b692 2023-08-23 Pavel Safronov | 22 | for key, value := range rawMap { |
352a2a02 2023-08-22 Pavel Safronov | 23 | if key == "findstone" { |
74d9b692 2023-08-23 Pavel Safronov | 24 | err = json.Unmarshal(value, &c.Beers) |
352a2a02 2023-08-22 Pavel Safronov | 25 | if err != nil { |
| 26 | return err | |
| 27 | } | |
| 28 | } | |
| 29 | } | |
| 30 | return nil | |
| 31 | } | |
| 32 | ||
74d9b692 2023-08-23 Pavel Safronov | 33 | type BeersInfo struct { |
| 34 | brands map[string]Brand | |
| 35 | PackageTypes map[string]PackageType | |
352a2a02 2023-08-22 Pavel Safronov | 36 | } |
| 37 | ||
74d9b692 2023-08-23 Pavel Safronov | 38 | func (f *BeersInfo) UnmarshalJSON(data []byte) error { |
| 39 | var rawMap map[string]json.RawMessage | |
| 40 | err := json.Unmarshal(data, &rawMap) | |
352a2a02 2023-08-22 Pavel Safronov | 41 | if err != nil { |
| 42 | return err | |
| 43 | } | |
74d9b692 2023-08-23 Pavel Safronov | 44 | for key, value := range rawMap { |
352a2a02 2023-08-22 Pavel Safronov | 45 | if key == "brands" { |
74d9b692 2023-08-23 Pavel Safronov | 46 | err = json.Unmarshal(value, &f.brands) |
352a2a02 2023-08-22 Pavel Safronov | 47 | if err != nil { |
| 48 | return err | |
| 49 | } | |
| 50 | } else if key == "package_types" { | |
| 51 | err = json.Unmarshal(value, &f.PackageTypes) | |
| 52 | if err != nil { | |
| 53 | return err | |
| 54 | } | |
| 55 | } | |
| 56 | } | |
| 57 | return nil | |
| 58 | } | |
74d9b692 2023-08-23 Pavel Safronov | 59 | func (f *BeersInfo) FindBrandById(targetId string) (bool, Brand) { |
| 60 | for id, brand := range f.brands { | |
352a2a02 2023-08-22 Pavel Safronov | 61 | if id == targetId { |
| 62 | return true, brand | |
| 63 | } | |
| 64 | foundBrand, brand := brand.FindBrandById(targetId) | |
| 65 | if foundBrand { | |
| 66 | return true, brand | |
| 67 | } | |
| 68 | } | |
| 69 | return false, Brand{} | |
| 70 | } | |
| 71 | ||
59f69900 2023-08-27 Pavel Safronov | 72 | func GetBeersInfo() (BeersInfo, error) { |
| 73 | data, err := getCachedUrlFunc( | |
| 74 | "https://find.stonebrewing.com", | |
| 75 | "find-stone-beer-core", | |
| 76 | "2006-01-02", | |
| 77 | func(d []byte) ([]byte, error) { | |
| 78 | reader := bytes.NewReader(d) | |
| 79 | doc, err := html.Parse(reader) | |
| 80 | if err != nil { | |
| 81 | return nil, err | |
| 82 | } | |
| 83 | ||
| 84 | var findNodeWithText func(node *html.Node, text string) (bool, *html.Node) | |
| 85 | findNodeWithText = func(node *html.Node, text string) (bool, *html.Node) { | |
| 86 | if node.Type == html.TextNode && strings.Contains(node.Data, text) { | |
| 87 | return true, node | |
| 88 | } | |
| 89 | ||
| 90 | if node.FirstChild != nil { | |
| 91 | found, child := findNodeWithText(node.FirstChild, text) | |
| 92 | if found { | |
| 93 | return true, child | |
| 94 | } | |
| 95 | } | |
| 96 | ||
| 97 | if node.NextSibling != nil { | |
| 98 | found, sibling := findNodeWithText(node.NextSibling, text) | |
| 99 | if found { | |
| 100 | return true, sibling | |
| 101 | } | |
| 102 | } | |
| 103 | ||
| 104 | return false, nil | |
| 105 | } | |
| 106 | found, node := findNodeWithText(doc, "6376") | |
| 107 | if !found { | |
| 108 | return nil, errors.New("could not find node") | |
| 109 | } | |
| 110 | ||
| 111 | firstCurly := strings.Index(node.Data, "{") | |
| 112 | lastCurly := strings.LastIndex(node.Data, "}") | |
| 113 | jsonText := node.Data[firstCurly : lastCurly+1] | |
| 114 | return []byte(jsonText), nil | |
| 115 | }) | |
| 116 | if err != nil { | |
| 117 | return BeersInfo{}, err | |
| 118 | } | |
| 119 | ||
| 120 | var core = Core{} | |
| 121 | err = json.Unmarshal(data, &core) | |
| 122 | if err != nil { | |
| 123 | return BeersInfo{}, err | |
| 124 | } | |
| 125 | return core.Beers, nil | |
| 126 | } | |
| 127 | ||
352a2a02 2023-08-22 Pavel Safronov | 128 | type PackageType struct { |
| 129 | Name string `json:"name"` | |
| 130 | ReleaseDate int `json:"release_date"` | |
| 131 | } | |
| 132 | ||
| 133 | type Brand struct { | |
| 134 | Parent *Brand | |
| 135 | Name string `json:"name"` | |
| 136 | ReleaseDate int `json:"release_date"` | |
| 137 | BrandColor string `json:"brand_color"` | |
| 138 | BeerStyle string `json:"beer_style"` | |
| 139 | Logo string `json:"logo"` | |
| 140 | Abv string `json:"abv"` | |
| 141 | Children map[string]Brand `json:"children"` | |
| 142 | } | |
| 143 | ||
| 144 | func (b *Brand) UnmarshalJSON(data []byte) error { | |
| 145 | brand := &struct { | |
| 146 | Name string `json:"name"` | |
| 147 | ReleaseDate int `json:"release_date"` | |
| 148 | BrandColor string `json:"brand_color"` | |
| 149 | BeerStyle string `json:"beer_style"` | |
| 150 | Logo string `json:"logo"` | |
| 151 | Abv string `json:"abv"` | |
| 152 | Children map[string]json.RawMessage `json:"children"` | |
| 153 | }{} | |
| 154 | json.Unmarshal(data, &brand) | |
| 155 | ||
| 156 | newBrand := Brand{ | |
| 157 | Name: brand.Name, | |
| 158 | ReleaseDate: brand.ReleaseDate, | |
| 159 | BrandColor: brand.BrandColor, | |
| 160 | BeerStyle: brand.BeerStyle, | |
| 161 | Logo: brand.Logo, | |
| 162 | Abv: brand.Abv, | |
| 163 | } | |
| 164 | ||
| 165 | newBrand.Children = map[string]Brand{} | |
| 166 | for key, value := range brand.Children { | |
| 167 | var childName string | |
| 168 | err := json.Unmarshal(value, &childName) | |
| 169 | if err != nil { | |
| 170 | return err | |
| 171 | } | |
| 172 | childBrand := Brand{ | |
| 173 | Name: childName, | |
| 174 | ReleaseDate: brand.ReleaseDate, | |
| 175 | BrandColor: brand.BrandColor, | |
| 176 | BeerStyle: brand.BeerStyle, | |
| 177 | Logo: brand.Logo, | |
| 178 | Abv: brand.Abv, | |
| 179 | Parent: &newBrand, | |
| 180 | } | |
| 181 | newBrand.Children[key] = childBrand | |
| 182 | } | |
| 183 | ||
| 184 | *b = newBrand | |
| 185 | return nil | |
| 186 | } | |
| 187 | ||
| 188 | func (b *Brand) FindBrandById(targetId string) (bool, Brand) { | |
| 189 | for id, child := range b.Children { | |
| 190 | if id == targetId { | |
| 191 | return true, child | |
| 192 | } | |
| 193 | foundBrand, brand := child.FindBrandById(targetId) | |
| 194 | if foundBrand { | |
| 195 | return true, brand | |
| 196 | } | |
| 197 | } | |
| 198 | return false, Brand{} | |
| 199 | } |