find-stone-beer / files / beers-info.go
beers-info.go
| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "encoding/json" |
| 6 | "errors" |
| 7 | "strings" |
| 8 | |
| 9 | "golang.org/x/net/html" |
| 10 | ) |
| 11 | |
| 12 | type Core struct { |
| 13 | Beers BeersInfo |
| 14 | } |
| 15 | |
| 16 | func (c *Core) UnmarshalJSON(data []byte) error { |
| 17 | var rawMap map[string]json.RawMessage |
| 18 | err := json.Unmarshal(data, &rawMap) |
| 19 | if err != nil { |
| 20 | return err |
| 21 | } |
| 22 | for key, value := range rawMap { |
| 23 | if key == "findstone" { |
| 24 | err = json.Unmarshal(value, &c.Beers) |
| 25 | if err != nil { |
| 26 | return err |
| 27 | } |
| 28 | } |
| 29 | } |
| 30 | return nil |
| 31 | } |
| 32 | |
| 33 | type BeersInfo struct { |
| 34 | brands map[string]Brand |
| 35 | PackageTypes map[string]PackageType |
| 36 | } |
| 37 | |
| 38 | func (f *BeersInfo) UnmarshalJSON(data []byte) error { |
| 39 | var rawMap map[string]json.RawMessage |
| 40 | err := json.Unmarshal(data, &rawMap) |
| 41 | if err != nil { |
| 42 | return err |
| 43 | } |
| 44 | for key, value := range rawMap { |
| 45 | if key == "brands" { |
| 46 | err = json.Unmarshal(value, &f.brands) |
| 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 | } |
| 59 | func (f *BeersInfo) FindBrandById(targetId string) (bool, Brand) { |
| 60 | for id, brand := range f.brands { |
| 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 | |
| 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 | |
| 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 | } |