From d7f0a68b08e1178de0b312698b7f343bc446a02d Mon Sep 17 00:00:00 2001 From: Felipe M Date: Thu, 29 Oct 2020 13:50:27 +0100 Subject: [PATCH] WIP --- .gitignore | 1 + Makefile | 2 + go.mod | 3 + main.go | 7 ++ pkg/cli/cli.go | 94 ++++++++++++++++++++ pkg/games/structs.go | 13 +++ pkg/helpers/dir.go | 20 +++++ pkg/helpers/files.go | 55 ++++++++++++ pkg/helpers/slice.go | 13 +++ pkg/providers/steam/steam.go | 165 +++++++++++++++++++++++++++++++++++ 10 files changed, 373 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 go.mod create mode 100644 main.go create mode 100644 pkg/cli/cli.go create mode 100644 pkg/games/structs.go create mode 100644 pkg/helpers/dir.go create mode 100644 pkg/helpers/files.go create mode 100644 pkg/helpers/slice.go create mode 100644 pkg/providers/steam/steam.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd0df9c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +Output \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a143777 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +build: + go build ./... \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ffa2b83 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/fmartingr/games-screenshot-mananger + +go 1.15 diff --git a/main.go b/main.go new file mode 100644 index 0000000..60aa3bf --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/fmartingr/games-screenshot-mananger/pkg/cli" + +func main() { + cli.Start() +} diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go new file mode 100644 index 0000000..97fec5d --- /dev/null +++ b/pkg/cli/cli.go @@ -0,0 +1,94 @@ +package cli + +import ( + "bytes" + "flag" + "log" + "os" + "path" + "strconv" + + "github.com/fmartingr/games-screenshot-mananger/pkg/games" + "github.com/fmartingr/games-screenshot-mananger/pkg/helpers" + "github.com/fmartingr/games-screenshot-mananger/pkg/providers/steam" +) + +var AllowedProviders = [...]string{"steam"} + +const OutputPath string = "~/Developer/games-screenshot-manager/Output" + +func main() { + Start() +} + +func Start() { + var provider = flag.String("provider", "steam", "steam") + flag.Parse() + if helpers.SliceContainsString(AllowedProviders[:], *provider, nil) { + games := getGamesFromProvider(*provider) + processGames(games) + } else { + log.Printf("Provider %s not found!", *provider) + } +} + +func getGamesFromProvider(provider string) []games.Game { + var games []games.Game + if provider == "steam" { + games = append(games, steam.GetGames()...) + } + return games +} + +func processGames(games []games.Game) { + dryRun := false + + for _, game := range games { + destinationPath := path.Join(helpers.ExpandUser(OutputPath), game.Platform) + if len(game.Name) > 0 { + destinationPath = path.Join(destinationPath, game.Name) + } else { + log.Printf("[IMPORTANT] Game ID %d has no name!", game.ID) + destinationPath = path.Join(destinationPath, strconv.FormatUint(game.ID, 10)) + } + + // Check if folder exists + if _, err := os.Stat(destinationPath); os.IsNotExist(err) { + os.MkdirAll(destinationPath, 0711) + } + + log.Printf("=> Proceesing screenshots for %s", game.Name) + for _, screenshot := range game.Screenshots { + fileStat, statErr := os.Stat(screenshot.Path) + if statErr != nil { + log.Fatal(statErr) + } + + destinationPath := path.Join(destinationPath, fileStat.ModTime().Format("2006-01-02_15-04-05")+path.Ext(screenshot.Path)) + + if _, err := os.Stat(destinationPath); !os.IsNotExist(err) { + sourceMd5, err := helpers.Md5File(screenshot.Path) + if err != nil { + log.Fatal(err) + continue + } + destinationMd5, err := helpers.Md5File(destinationPath) + if err != nil { + log.Fatal(err) + continue + } + + if bytes.Compare(sourceMd5, destinationMd5) != 0 { + // Images are not equal, we should copy it anyway, but how? + log.Println("Found different screenshot with equal timestamp for game ", game.Name, screenshot.Path) + } + + } else { + if !dryRun { + helpers.CopyFile(screenshot.Path, destinationPath) + } + } + } + } + +} diff --git a/pkg/games/structs.go b/pkg/games/structs.go new file mode 100644 index 0000000..d63ee80 --- /dev/null +++ b/pkg/games/structs.go @@ -0,0 +1,13 @@ +package games + +type Game struct { + ID uint64 + Name string + Platform string + Provider string + Screenshots []Screenshot +} + +type Screenshot struct { + Path string +} diff --git a/pkg/helpers/dir.go b/pkg/helpers/dir.go new file mode 100644 index 0000000..e9c8fe2 --- /dev/null +++ b/pkg/helpers/dir.go @@ -0,0 +1,20 @@ +package helpers + +import ( + "os/user" + "path/filepath" + "strings" +) + +func ExpandUser(providedPath string) string { + var path string + usr, _ := user.Current() + dir := usr.HomeDir + + if providedPath == "~" { + path = dir + } else if strings.HasPrefix(providedPath, "~/") { + path = filepath.Join(dir, providedPath[2:]) + } + return path +} diff --git a/pkg/helpers/files.go b/pkg/helpers/files.go new file mode 100644 index 0000000..0847247 --- /dev/null +++ b/pkg/helpers/files.go @@ -0,0 +1,55 @@ +package helpers + +import ( + "crypto/md5" + "fmt" + "io" + "log" + "os" +) + +func CopyFile(src, dst string) (int64, error) { + sourceFileStat, err := os.Stat(src) + if err != nil { + return 0, err + } + + if !sourceFileStat.Mode().IsRegular() { + return 0, fmt.Errorf("%s is not a regular file", src) + } + + source, err := os.Open(src) + if err != nil { + return 0, err + } + defer source.Close() + + // Check if destination exists + if _, err := os.Stat(dst); !os.IsNotExist(err) { + log.Printf("- %s already exists, skipping...", dst) + return 0, nil + } + + destination, err := os.Create(dst) + if err != nil { + return 0, err + } + defer destination.Close() + nBytes, err := io.Copy(destination, source) + return nBytes, err +} + +func Md5File(src string) ([]byte, error) { + f, err := os.Open(src) + if err != nil { + return nil, err + } + defer f.Close() + + h := md5.New() + if _, err := io.Copy(h, f); err != nil { + return nil, err + } + + return h.Sum(nil), nil +} diff --git a/pkg/helpers/slice.go b/pkg/helpers/slice.go new file mode 100644 index 0000000..35a7339 --- /dev/null +++ b/pkg/helpers/slice.go @@ -0,0 +1,13 @@ +package helpers + +func SliceContainsString(slice []string, s string, modifier func(s string) string) bool { + for _, item := range slice { + if item == s { + return true + } + if modifier != nil && modifier(item) == s { + return true + } + } + return false +} diff --git a/pkg/providers/steam/steam.go b/pkg/providers/steam/steam.go new file mode 100644 index 0000000..56fdf4e --- /dev/null +++ b/pkg/providers/steam/steam.go @@ -0,0 +1,165 @@ +package steam + +import ( + "encoding/json" + "errors" + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + "runtime" + "strconv" + "strings" + + "github.com/fmartingr/games-screenshot-mananger/pkg/games" + "github.com/fmartingr/games-screenshot-mananger/pkg/helpers" +) + +const providerName string = "steam" + +type SteamApp struct { + AppID uint64 `json:"appid"` + Name string `json:"name"` +} + +type SteamAppList struct { + Apps []SteamApp `json:"apps"` +} + +func (appList SteamAppList) FindID(id uint64) (SteamApp, error) { + GameIDNotFound := errors.New("Game ID not found") + for _, game := range appList.Apps { + if game.AppID == id { + return game, nil + } + } + return SteamApp{}, GameIDNotFound +} + +type SteamAppListResponse struct { + AppList SteamAppList `json:"applist"` +} + +func GetSteamAppsList(c chan SteamAppList) { + log.Println("Updating steam game list...") + steamGetAppListURL, _ := url.Parse("https://api.steampowered.com/ISteamApps/GetAppList/v2/") + request := http.Request{ + Method: "GET", + URL: steamGetAppListURL, + Header: map[string][]string{ + "User-Agent": {"games-screenshot-manager/0.0.1"}, + }, + ProtoMajor: 2, + ProtoMinor: 1, + } + response, err := http.DefaultClient.Do(&request) + if err != nil { + panic(err) + } + + if response.Body != nil { + defer response.Body.Close() + } + + body, err := ioutil.ReadAll(response.Body) + if err != nil { + panic(err) + } + + steamListResponse := SteamAppListResponse{} + jsonErr := json.Unmarshal(body, &steamListResponse) + if jsonErr != nil { + log.Fatal(jsonErr) + } + + log.Printf("Updated Steam game list. Found %d games.", len(steamListResponse.AppList.Apps)) + + c <- steamListResponse.AppList +} + +func GuessUsers() []string { + var users []string + + if runtime.GOOS == "linux" { + if _, err := os.Stat(helpers.ExpandUser("~/.local/share/Steam/userdata")); !os.IsNotExist(err) { + files, err := ioutil.ReadDir(helpers.ExpandUser("~/.local/share/Steam/userdata")) + if err != nil { + log.Fatal(err) + } + + for _, file := range files { + if _, err := strconv.ParseInt(file.Name(), 10, 64); err == nil { + log.Printf("Found local install Steam user: %s", file.Name()) + users = append(users, file.Name()) + } + } + } + } + return users +} + +func GetGamesFromUser(user string) []uint64 { + log.Println("Getting Steam games for user: " + user) + var userGames []uint64 + path := helpers.ExpandUser("~/.local/share/Steam/userdata/" + user + "/760/remote") + + if runtime.GOOS == "linux" { + if _, err := os.Stat(path); !os.IsNotExist(err) { + files, err := ioutil.ReadDir(path) + if err != nil { + log.Fatal(err) + } + + for _, file := range files { + // len(file.Name()) == 20 -> Custom added Game to steam + gameID, err := strconv.ParseUint(file.Name(), 10, 64) + if err == nil { + userGames = append(userGames, gameID) + } + } + } + } + return userGames +} + +func GetScreenshotsForGame(user string, game *games.Game) { + path := helpers.ExpandUser("~/.local/share/Steam/userdata/" + user + "/760/remote/" + strconv.FormatUint(game.ID, 10) + "/screenshots") + files, err := ioutil.ReadDir(path) + if err != nil { + log.Fatal(err) + } + + for _, file := range files { + if strings.Contains(file.Name(), ".jpg") { + game.Screenshots = append(game.Screenshots, games.Screenshot{Path: path + "/" + file.Name()}) + // log.Printf("Found screenshot for user %s and game %d: %s", user, game.ID, path+"/"+file.Name()) + } + } + + if len(game.Screenshots) > 0 { + log.Printf("Found %d screenshots", len(game.Screenshots)) + } +} + +func GetGames() []games.Game { + var localGames []games.Game + c := make(chan SteamAppList) + go GetSteamAppsList(c) + users := GuessUsers() + steamApps := <-c + for _, userID := range users { + userGames := GetGamesFromUser(userID) + for _, userGameID := range userGames { + steamGame, err := steamApps.FindID(userGameID) + if err != nil { + log.Print("[ERROR] Steam game ID not found: ", userGameID) + } + userGame := games.Game{ID: userGameID, Name: steamGame.Name, Provider: providerName, Platform: "PC"} + log.Printf("Found Steam game for user %s: %s (%d)", userID, userGame.Name, userGame.ID) + GetScreenshotsForGame(userID, &userGame) + localGames = append(localGames, userGame) + } + } + return localGames +}