package api import ( "fmt" "golang.org/x/net/html" "log/slog" "net/http" "regexp" ) type GogStruct struct { url string baseUrl string idPrefix string headers map[string]string deals DealsMap logger *slog.Logger } func NewGogApi(logger *slog.Logger) GogStruct { gog := GogStruct{ url: "https://www.gog.com/en/games?priceRange=0,0&discounted=true", baseUrl: "https://www.gog.com/game/", idPrefix: "gog-", headers: make(map[string]string), deals: make(map[string]Deal), logger: logger, } gog.headers["Accept-Language"] = "en" gog.headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0" return gog } func (e GogStruct) 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 } for key, value := range e.headers { reqStore.Header.Set(key, value) } resStore, err := client.Do(reqStore) if err != nil { return err } bodyStore := html.NewTokenizer(resStore.Body) regexAppid, err := regexp.Compile(`/\w{2}/game/([-\w]+)`) if err != nil { return err } var appIDs []string func() { for { tt := bodyStore.Next() switch { case tt == html.ErrorToken: // file end or error return case tt == html.StartTagToken: t := bodyStore.Token() if t.Data != "a" { // only tag continue } for _, a := range t.Attr { if !(a.Key == "class" && a.Val == "product-tile product-tile--grid") { continue } for _, attr := range t.Attr { if attr.Key != "href" { continue } appID := regexAppid.FindStringSubmatch(attr.Val) if len(appID) < 1 { continue } appIDs = append(appIDs, appID[1]) } } } } }() for _, appID := range appIDs { reqGame, err := http.NewRequest("GET", fmt.Sprintf("%v%v", e.baseUrl, appID), nil) if err != nil { return err } for key, value := range e.headers { reqGame.Header.Set(key, value) } resGame, err := client.Do(reqGame) if err != nil { return err } bodyGame := html.NewTokenizer(resGame.Body) title, image := gogParseMeta(bodyGame) url := fmt.Sprintf("%v%v", e.baseUrl, appID) id := fmt.Sprintf("%v%v", e.idPrefix, appID) e.deals[id] = Deal{ Id: id, Title: title, Url: url, Image: image, } } return nil } func gogParseMeta(htmlBody *html.Tokenizer) (string, string) { var title, image string func() { for { tt := htmlBody.Next() switch { case tt == html.ErrorToken: // file end or error return case tt == html.StartTagToken: t := htmlBody.Token() if t.Data != "meta" { continue } for _, a := range t.Attr { if a.Key == "property" && a.Val == "og:title" { for _, c := range t.Attr { if c.Key == "content" { title = c.Val } } } if a.Key == "property" && a.Val == "og:image" { for _, c := range t.Attr { if c.Key == "content" { image = c.Val } } } } if title != "" && image != "" { return } } } }() return title, image } func (e GogStruct) Get() []Deal { var deals []Deal for _, deal := range e.deals { deals = append(deals, deal) } return deals }