package bot import ( "context" "database/sql" "encoding/json" "fmt" "io" "net/http" "time" ) // SteamFreeConfig is the config shape for bot_type "steamfree". type SteamFreeConfig struct { ChannelID string `json:"channel_id"` PollMinutes int `json:"poll_minutes"` } // SteamFreeBot polls Steam's featured categories for 100%-off games. func SteamFreeBot(ctx context.Context, _ *sql.DB, raw json.RawMessage, send SendMessageFunc) { var cfg SteamFreeConfig if err := json.Unmarshal(raw, &cfg); err != nil || cfg.ChannelID == "" { return } if cfg.PollMinutes <= 0 { cfg.PollMinutes = 30 } seen := map[int]bool{} ticker := time.NewTicker(time.Duration(cfg.PollMinutes) * time.Minute) defer ticker.Stop() // Poll immediately on start pollSteam(cfg.ChannelID, seen, send) for { select { case <-ctx.Done(): return case <-ticker.C: pollSteam(cfg.ChannelID, seen, send) } } } func pollSteam(channelID string, seen map[int]bool, send SendMessageFunc) { games, err := fetchFreeGames() if err != nil { return // ponytail: silent on error, logs add noise } for _, g := range games { if seen[g.ID] { continue } seen[g.ID] = true msg := fmt.Sprintf( "🎮 **FREE ON STEAM** 🎮\n**%s**\n~~$%.2f~~ → **FREE**\nhttps://store.steampowered.com/app/%d", g.Name, float64(g.OriginalPrice)/100, g.ID, ) send(channelID, msg) time.Sleep(500 * time.Millisecond) } } type featuredCategories struct { Specials struct { Items []struct { ID int `json:"id"` Name string `json:"name"` DiscountPct int `json:"discount_percent"` FinalPrice int `json:"final_price"` OriginalPrice int `json:"original_price"` } `json:"items"` } `json:"specials"` } func fetchFreeGames() ([]struct { ID int `json:"id"` Name string `json:"name"` DiscountPct int `json:"discount_percent"` FinalPrice int `json:"final_price"` OriginalPrice int `json:"original_price"` }, error) { client := &http.Client{Timeout: 15 * time.Second} resp, err := client.Get("https://store.steampowered.com/api/featuredcategories?cc=us&l=english") if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, fmt.Errorf("steam: %d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } var cats featuredCategories if err := json.Unmarshal(body, &cats); err != nil { return nil, err } var free []struct { ID int `json:"id"` Name string `json:"name"` DiscountPct int `json:"discount_percent"` FinalPrice int `json:"final_price"` OriginalPrice int `json:"original_price"` } for _, item := range cats.Specials.Items { if item.DiscountPct == 100 && item.OriginalPrice > 0 { free = append(free, item) } } return free, nil }