147 lines
3 KiB
Go
147 lines
3 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"golang.org/x/net/html"
|
||
|
"io"
|
||
|
"net/http"
|
||
|
"regexp"
|
||
|
)
|
||
|
|
||
|
type SteamStruct struct {
|
||
|
url string
|
||
|
baseUrl string
|
||
|
apiUrl string
|
||
|
idPrefix string
|
||
|
deals DealsMap
|
||
|
}
|
||
|
|
||
|
func newSteamApi() SteamStruct {
|
||
|
return SteamStruct{
|
||
|
url: "https://store.steampowered.com/search/results?force_infinite=1&maxprice=free&specials=1",
|
||
|
baseUrl: "https://store.steampowered.com/app/",
|
||
|
apiUrl: "https://store.steampowered.com/api/appdetails?appids=",
|
||
|
idPrefix: "steam-",
|
||
|
deals: make(map[string]Deal),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type steamApiBodyGame struct {
|
||
|
Success bool `json:"success"`
|
||
|
Data struct {
|
||
|
Type string `json:"type"`
|
||
|
Name string `json:"name"`
|
||
|
IsFree bool `json:"is_free"`
|
||
|
PriceOverview struct {
|
||
|
Currency string `json:"currency"`
|
||
|
Initial int `json:"initial"`
|
||
|
Final int `json:"final"`
|
||
|
DiscountPercent int `json:"discount_percent"`
|
||
|
InitialFormatted string `json:"initial_formatted"`
|
||
|
FinalFormatted string `json:"final_formatted"`
|
||
|
} `json:"price_overview"`
|
||
|
Platforms struct {
|
||
|
Windows bool `json:"windows"`
|
||
|
Mac bool `json:"mac"`
|
||
|
Linux bool `json:"linux"`
|
||
|
} `json:"platforms"`
|
||
|
} `json:"data"`
|
||
|
}
|
||
|
|
||
|
type steamApiBody map[string]steamApiBodyGame
|
||
|
|
||
|
func (e SteamStruct) load() error {
|
||
|
client := &http.Client{}
|
||
|
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)
|
||
|
|
||
|
// could also search over each #search_resultsRows element instead of regex
|
||
|
regexAppid, err := regexp.Compile(`https://store\.steampowered\.com/app/(\d+)`)
|
||
|
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" {
|
||
|
continue
|
||
|
}
|
||
|
for _, a := range t.Attr {
|
||
|
if a.Key != "href" {
|
||
|
continue
|
||
|
}
|
||
|
appID := regexAppid.FindStringSubmatch(a.Val)
|
||
|
if len(appID) < 1 {
|
||
|
continue
|
||
|
}
|
||
|
appIDs = append(appIDs, appID[1])
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
for _, appID := range appIDs {
|
||
|
reqApi, err := http.NewRequest("GET", fmt.Sprintf("%v%v", e.apiUrl, appID), nil)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
resApi, err := client.Do(reqApi)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
bodyApi, err := io.ReadAll(resApi.Body)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
var data steamApiBody
|
||
|
err = json.Unmarshal(bodyApi, &data)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if game, ok := data[appID]; ok {
|
||
|
if game.Data.Type != "game" {
|
||
|
continue
|
||
|
}
|
||
|
id := fmt.Sprintf("%v%v", e.idPrefix, appID)
|
||
|
title := game.Data.Name
|
||
|
url := fmt.Sprintf("%v%v", e.baseUrl, appID)
|
||
|
|
||
|
e.deals[id] = Deal{
|
||
|
Id: id,
|
||
|
Title: title,
|
||
|
Url: url,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (e SteamStruct) get() []Deal {
|
||
|
var deals []Deal
|
||
|
for _, deal := range e.deals {
|
||
|
deals = append(deals, deal)
|
||
|
}
|
||
|
return deals
|
||
|
}
|