package main import ( "encoding/json" "fmt" "golang.org/x/net/html" "net/http" "regexp" ) type HumbleBundleStruct struct { url string baseUrl string idPrefix string deals DealsMap } func newHumbleBundleApi() HumbleBundleStruct { return HumbleBundleStruct{ url: "https://www.humblebundle.com/", baseUrl: "https://www.humblebundle.com/store/", idPrefix: "humblebundle-", deals: make(map[string]Deal), } } type humblebundleJsonBody struct { Mosaic []struct { Products []struct { ProductUrl string `json:"product_url,omitempty"` Highlights []string `json:"highlights,omitempty"` TileName string `json:"tile_name,omitempty"` ProductTitle *string `json:"product_title,omitempty"` Type string `json:"type"` Category string `json:"category,omitempty"` EndDateDatetime string `json:"end_date|datetime,omitempty"` OperatingSystems []string `json:"operating_systems,omitempty"` Platforms []string `json:"platforms,omitempty"` } `json:"products"` } `json:"mosaic"` } func (e HumbleBundleStruct) load() error { client := &http.Client{} // might have to add a cookie at a later time but currently works without // "Cookie", "gog_lc=GB_GBP_en-US" or "Accept-Language", "en" reqStore, err := http.NewRequest("GET", e.url, nil) if err != nil { return err } resStore, err := client.Do(reqStore) if err != nil { return err } bodyStore := html.NewTokenizer(resStore.Body) var data humblebundleJsonBody err = func() error { for { tt := bodyStore.Next() switch { case tt == html.ErrorToken: // file end or error return nil case tt == html.StartTagToken: t := bodyStore.Token() if t.Data != "script" { continue } for _, a := range t.Attr { if !(a.Key == "id" && a.Val == "webpack-json-data") { continue } if tt = bodyStore.Next(); tt != html.TextToken { continue } err = json.Unmarshal([]byte(bodyStore.Token().Data), &data) if err != nil { return err } } } } }() if err != nil { return err } regexAppid, err := regexp.Compile(`/store/([-\w]+)`) if err != nil { return err } for _, products := range data.Mosaic { for _, product := range products.Products { if !contains(product.Highlights, "FREE WHILE SUPPLIES LAST") { continue } appID := regexAppid.FindStringSubmatch(product.ProductUrl) if len(appID) < 1 { continue } id := fmt.Sprintf("%v%v", e.idPrefix, appID[1]) title := product.TileName url := fmt.Sprintf("%v%v", e.baseUrl, appID[1]) e.deals[id] = Deal{ Id: id, Title: title, Url: url, } } } return nil } func contains(s []string, str string) bool { for _, v := range s { if v == str { return true } } return false } func (e HumbleBundleStruct) get() []Deal { var deals []Deal for _, deal := range e.deals { deals = append(deals, deal) } return deals }