Rewrite Pastel from Python to Go with watchlist feature
Full rewrite of the gaming deals Matrix bot in Go for better performance and a single static binary. Includes all original functionality (CheapShark, ITAD, Epic free games, multi-currency pricing, dedup, preflight checks, presence heartbeat) plus a new watchlist feature where users can DM the bot to watch for specific game deals with 180-day auto-expiry. New: systemd service file, DB migration script for Python-to-Go transition. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
123
internal/deals/cheapshark.go
Normal file
123
internal/deals/cheapshark.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package deals
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var storeNames = map[string]string{
|
||||
"1": "Steam",
|
||||
"7": "GOG",
|
||||
"11": "Humble Store",
|
||||
"23": "GreenManGaming",
|
||||
}
|
||||
|
||||
type CheapSharkDeal struct {
|
||||
DealID string
|
||||
GameID string
|
||||
Title string
|
||||
SalePrice float64
|
||||
NormalPrice float64
|
||||
Savings float64
|
||||
DealRating float64
|
||||
StoreID string
|
||||
StoreName string
|
||||
LastChange int64
|
||||
SteamAppID string
|
||||
DealURL string
|
||||
IsHistLow bool
|
||||
DedupID string
|
||||
}
|
||||
|
||||
type cheapSharkRaw struct {
|
||||
DealID string `json:"dealID"`
|
||||
GameID string `json:"gameID"`
|
||||
Title string `json:"title"`
|
||||
SalePrice string `json:"salePrice"`
|
||||
NormalPrice string `json:"normalPrice"`
|
||||
Savings string `json:"savings"`
|
||||
DealRating string `json:"dealRating"`
|
||||
StoreID string `json:"storeID"`
|
||||
LastChange int64 `json:"lastChange"`
|
||||
SteamAppID string `json:"steamAppID"`
|
||||
}
|
||||
|
||||
// FetchCheapSharkDeals fetches deals from CheapShark API.
|
||||
func FetchCheapSharkDeals(maxPrice float64, pageSize int) ([]CheapSharkDeal, error) {
|
||||
url := fmt.Sprintf(
|
||||
"https://www.cheapshark.com/api/1.0/deals?storeID=1,7,11,23&upperPrice=%d&sortBy=recent&desc=1&pageSize=%d",
|
||||
int(maxPrice), pageSize,
|
||||
)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cheapshark request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("cheapshark returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var raw []cheapSharkRaw
|
||||
if err := json.NewDecoder(resp.Body).Decode(&raw); err != nil {
|
||||
return nil, fmt.Errorf("cheapshark decode failed: %w", err)
|
||||
}
|
||||
|
||||
var deals []CheapSharkDeal
|
||||
for _, d := range raw {
|
||||
sale, _ := strconv.ParseFloat(d.SalePrice, 64)
|
||||
normal, _ := strconv.ParseFloat(d.NormalPrice, 64)
|
||||
savings, _ := strconv.ParseFloat(d.Savings, 64)
|
||||
rating, _ := strconv.ParseFloat(d.DealRating, 64)
|
||||
|
||||
storeName := storeNames[d.StoreID]
|
||||
if storeName == "" {
|
||||
storeName = "Unknown"
|
||||
}
|
||||
|
||||
deals = append(deals, CheapSharkDeal{
|
||||
DealID: d.DealID,
|
||||
GameID: d.GameID,
|
||||
Title: d.Title,
|
||||
SalePrice: sale,
|
||||
NormalPrice: normal,
|
||||
Savings: savings,
|
||||
DealRating: rating,
|
||||
StoreID: d.StoreID,
|
||||
StoreName: storeName,
|
||||
LastChange: d.LastChange,
|
||||
SteamAppID: d.SteamAppID,
|
||||
DealURL: fmt.Sprintf("https://www.cheapshark.com/redirect?dealID=%s", d.DealID),
|
||||
DedupID: fmt.Sprintf("cheapshark-%s-%d", d.GameID, d.LastChange),
|
||||
})
|
||||
}
|
||||
|
||||
return deals, nil
|
||||
}
|
||||
|
||||
// FilterCheapSharkDeals filters deals by rating, discount, and price thresholds.
|
||||
func FilterCheapSharkDeals(deals []CheapSharkDeal, minRating float64, minDiscount int, maxPrice float64) []CheapSharkDeal {
|
||||
var filtered []CheapSharkDeal
|
||||
for _, d := range deals {
|
||||
discount := int(math.Floor(d.Savings))
|
||||
if discount < minDiscount {
|
||||
slog.Debug("cheapshark: skipping (low discount)", "title", d.Title, "discount", discount)
|
||||
continue
|
||||
}
|
||||
if d.DealRating < minRating && d.DealRating != 0 {
|
||||
slog.Debug("cheapshark: skipping (low rating)", "title", d.Title, "rating", d.DealRating)
|
||||
continue
|
||||
}
|
||||
if d.SalePrice > maxPrice {
|
||||
slog.Debug("cheapshark: skipping (too expensive)", "title", d.Title, "price", d.SalePrice)
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, d)
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
Reference in New Issue
Block a user