Files
stimvisor/steam/games.go

188 lines
5.0 KiB
Go

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
}