package steam import ( "encoding/json" "fmt" "io" "net/http" "os" "path/filepath" "slices" "strconv" "github.com/chenasraf/stimvisor/common" "github.com/chenasraf/stimvisor/dirs" "github.com/chenasraf/stimvisor/logger" "github.com/Goldziher/go-utils/sliceutils" ) // GameInfo represents the information about a game. type GameInfo struct { Id string `json:"id"` Name string `json:"name"` InstallDir string `json:"installDir"` Description string `json:"description"` ShortDescription string `json:"shortDescription"` Website string `json:"website"` BackgroundImage string `json:"backgroundImage"` CapsuleImage string `json:"capsuleImage"` Categories []string `json:"categories"` } // GetGameInfo retrieves the information of a game given its ID. func GetGameInfo(gameId string) (GameInfo, error) { gameDir, err := GetGameDir(gameId) if err != nil { return GameInfo{}, err } raw, err := loadGameInfo(gameId) if err != nil { return GameInfo{}, err } website := "" if raw["website"] != nil { website = raw["website"].(string) } categories := []string{} if raw["categories"] != nil { categories = sliceutils.Map( raw["categories"].([]interface{}), func(v interface{}, i int, l []interface{}) string { val, ok := v.(map[string]interface{}) if !ok { return "" } return val["description"].(string) }) } return GameInfo{ Id: gameId, Name: raw["name"].(string), InstallDir: gameDir, Description: raw["detailed_description"].(string), ShortDescription: raw["short_description"].(string), Website: website, BackgroundImage: raw["background"].(string), CapsuleImage: raw["capsule_image"].(string), Categories: categories, }, nil } // GetGameInfoCacheDir returns the directory path for caching game information. func GetGameInfoCacheDir() string { cacheDir := common.GetCacheDir() return filepath.Join(cacheDir, "gameinfo") } // loadGameInfo loads the game information from the cache or fetches it if not available. func loadGameInfo(gameId string) (map[string]interface{}, error) { info := make(map[string]interface{}) cachePath := filepath.Join(GetGameInfoCacheDir(), gameId+".json") var err error if _, err = os.Stat(cachePath); os.IsNotExist(err) { info, err = fetchGameInfo(gameId) if err == nil { return info, nil } } if err != nil { return info, err } f, err := os.ReadFile(cachePath) if err != nil { return info, err } json.Unmarshal(f, &info) logger.Debug("Loaded game info for %s from cache", gameId) return info, nil } const STEAM_API_URL = "https://store.steampowered.com/api/appdetails?appids=%s" // fetchGameInfo fetches the game information from the Steam API and caches it. func fetchGameInfo(gameId string) (map[string]interface{}, error) { os.MkdirAll(GetGameInfoCacheDir(), 0755) cachePath := filepath.Join(GetGameInfoCacheDir(), gameId+".json") url := fmt.Sprintf(STEAM_API_URL, gameId) logger.Info("Fetching game info for %s from %s", gameId, url) resp, err := http.Get(url) if err != nil { logger.FatalErr(err) panic(err) } defer resp.Body.Close() respBytes, err := io.ReadAll(resp.Body) respJson := make(map[string]interface{}) json.Unmarshal(respBytes, &respJson) if respJson[gameId] == nil { logger.Error("Failed to fetch game info for %s", gameId) return map[string]interface{}{}, fmt.Errorf("Failed to fetch game info for %s", gameId) } // extract result->gameId->data respGame := respJson[gameId].(map[string]interface{}) if respGame["success"] == false || respGame["data"] == nil { logger.Error("Failed to fetch game info for %s", gameId) return map[string]interface{}{}, fmt.Errorf("Failed to fetch game info for %s", gameId) } respGameData := respGame["data"].(map[string]interface{}) partBytes, _ := json.Marshal(respGameData) cacheFile, err := os.Create(cachePath) if err != nil { logger.FatalErr(err) panic(err) } defer cacheFile.Close() logger.Info("Caching game info for %s", gameId) cacheFile.WriteString(string(partBytes)) return respGameData, nil } // GetAllDirs returns a list of directories for all games of a specific Steam user by user ID. func GetAllDirs(userId string) ([]string, error) { userDir, err := dirs.GetUserDirectory(userId) if err != nil { return nil, err } entries, err := os.ReadDir(userDir) if err != nil { return nil, err } gameDirs := []string{} for _, entry := range entries { if !entry.IsDir() { continue } if slices.Contains(common.STEAM_INTERNAL_IDS, entry.Name()) { continue } if _, err := strconv.Atoi(entry.Name()); err != nil { continue } gameDir := fmt.Sprintf("%s/%s", userDir, entry.Name()) gameDirs = append(gameDirs, gameDir) } return gameDirs, nil } // GetGameDir returns the directory path for a specific game by game ID. func GetGameDir(gameId string) (string, error) { userDir, err := dirs.GetSteamUserDirectory() if err != nil { return "", err } return fmt.Sprintf("%s/%s", userDir, gameId), nil }