package steamimmich import ( "fmt" "log/slog" "slices" "sort" "strconv" ) type Config struct { BaseURL string APIKey string UserdataDir string DeiveID string Album string CacheFile string } func Run(config Config) int { screenshotFiles, err := listScreenshots(config.UserdataDir) if err != nil { slog.Error("Failed to scan for screenshots", "err", err) return 1 } if len(screenshotFiles) == 0 { slog.Info("No screenshots found") return 0 } immichClient := newImmichHttpClient(config.APIKey) localState, err := loadLocalState(config.CacheFile) if err != nil { slog.Error("Failed to load local cache", "err", err) return 1 } for appid, files := range screenshotFiles { gameName := getGameName(appid, localState.GameIDs) assetsInGame := make([]string, 0) for _, filepath := range files { idx := slices.IndexFunc(localState.Assets, func(e assetState) bool { return e.FilePath == filepath }) if idx != -1 { slog.Debug("Asset already uploaded", "path", filepath) continue } res, err := uploadToImmich(filepath, appid, config.BaseURL, config.DeiveID, &immichClient) if err != nil { slog.Error("Failed to upload to immich", "err", err) return 1 } slog.Info("Assets uploaded", "path", filepath, "id", res.ID) localState.Assets = append(localState.Assets, assetState{FilePath: filepath, ImmichID: res.ID}) assetsInGame = append(assetsInGame, res.ID) } err := setAssetMetadata(assetsInGame, fmt.Sprintf("Game: %s", gameName), config.BaseURL, immichClient) if err != nil { slog.Error("Failed to set assets metadata", "err", err) return 1 } if config.Album != "" { err = addAssetsToAlbum(assetsInGame, config.Album, config.BaseURL, immichClient) if err != nil { slog.Error("Failed to add assets to album", "err", err) return 1 } if len(assetsInGame) > 0 { slog.Info("Added assets to album", "count", len(assetsInGame)) } } } slog.Info("Finished uploading screenshots") err = saveLocalState(config.CacheFile, *localState) if err != nil { slog.Error("Failed to save local cache", "err", err) return 1 } return 0 } func Revalidate(config Config) int { immichClient := newImmichHttpClient(config.APIKey) localState, err := loadLocalState(config.CacheFile) if err != nil { slog.Error("Failed to load local cache", "err", err) return 1 } hashes, err := hashFiles(localState.Assets) if err != nil { slog.Error("Failed to generate hashes", "err", err) return 1 } idxToRemoveFromCache := []int{} rejected := 0 updated := 0 missingFromImmich := 0 missingFile := 0 // Prepare payload var idsToCheck = []assetBulkUploadCheckItem{} for i := range localState.Assets { hash := hashes[i] if hash == "" { idxToRemoveFromCache = append(idxToRemoveFromCache, i) missingFile += 1 } else { idsToCheck = append(idsToCheck, assetBulkUploadCheckItem{Checksum: hashes[i], Id: strconv.Itoa(i)}) } } results, err := bulkCheckAssets(idsToCheck, config.BaseURL, immichClient) if err != nil { slog.Error("Failed to bulk check hashes", "err", err) return 1 } for _, result := range results { i, err := strconv.Atoi(result.Id) if err != nil { slog.Error("What ? This should never happen", "err", err) return 1 } asset := localState.Assets[i] if asset.ImmichID != result.AssetId { slog.Info("Asset has the wrong Immich ID", "path", asset.FilePath, "id", result.AssetId) localState.Assets[i].ImmichID = result.AssetId updated += 1 } if result.Action == "reject" { rejected += 1 } else { idxToRemoveFromCache = append(idxToRemoveFromCache, i) missingFromImmich += 1 } } sort.Sort(sort.Reverse(sort.IntSlice(idxToRemoveFromCache))) for _, idx := range idxToRemoveFromCache { localState.Assets[idx] = localState.Assets[len(localState.Assets)-1] localState.Assets = localState.Assets[:len(localState.Assets)-1] } saveLocalState(config.CacheFile, *localState) slog.Info("Revalidated assets", "count", len(localState.Assets)) slog.Info("Assets present on immich", "count", rejected) slog.Info("Wrong metadata on cache", "count", updated) slog.Info("Missing on Immich", "count", missingFromImmich) slog.Info("Missing on local storage", "count", missingFile) if (updated + missingFromImmich + missingFromImmich) > 0 { slog.Info("Fixed cache. Run the normal upload command again. If error persists try removing cache file", "path", config.CacheFile) } return 0 }