/* Copyright © 2023-2025 Philipp Wolfer This file is part of Scotty. Scotty is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Scotty is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Scotty. If not, see . */ package spotifyhistory import ( "context" "sort" "time" "go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" ) type SpotifyHistoryBackend struct { archivePath string ignoreIncognito bool ignoreSkipped bool skippedMinSeconds int } func (b *SpotifyHistoryBackend) Name() string { return "spotify-history" } func (b *SpotifyHistoryBackend) Options() []models.BackendOption { return []models.BackendOption{{ Name: "archive-path", Label: i18n.Tr("Archive path"), Type: models.String, Default: "./my_spotify_data_extended.zip", MigrateFrom: "dir-path", }, { Name: "ignore-incognito", Label: i18n.Tr("Ignore listens in incognito mode"), Type: models.Bool, Default: "true", }, { Name: "ignore-skipped", Label: i18n.Tr("Ignore skipped listens"), Type: models.Bool, Default: "false", }, { Name: "ignore-min-duration-seconds", Label: i18n.Tr("Minimum playback duration for skipped tracks (seconds)"), Type: models.Int, Default: "30", }} } func (b *SpotifyHistoryBackend) InitConfig(config *config.ServiceConfig) error { b.archivePath = config.GetString("archive-path") // Backward compatibility if b.archivePath == "" { b.archivePath = config.GetString("dir-path") } b.ignoreIncognito = config.GetBool("ignore-incognito", true) b.ignoreSkipped = config.GetBool("ignore-skipped", false) b.skippedMinSeconds = config.GetInt("ignore-min-duration-seconds", 30) return nil } func (b *SpotifyHistoryBackend) ExportListens(ctx context.Context, oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.TransferProgress) { p := models.TransferProgress{ Export: &models.Progress{}, } archive, err := OpenHistoryArchive(b.archivePath) if err != nil { p.Export.Abort() progress <- p results <- models.ListensResult{Error: err} return } files, err := archive.GetHistoryFiles() if err != nil { p.Export.Abort() progress <- p results <- models.ListensResult{Error: err} return } fileCount := int64(len(files)) p.Export.Total = fileCount for i, f := range files { if err := ctx.Err(); err != nil { results <- models.ListensResult{Error: err} p.Export.Abort() progress <- p return } history, err := readHistoryFile(f.File) if err != nil { results <- models.ListensResult{Error: err} p.Export.Abort() progress <- p return } listens := history.AsListenList(ListenListOptions{ IgnoreIncognito: b.ignoreIncognito, IgnoreSkipped: b.ignoreSkipped, skippedMinSeconds: b.skippedMinSeconds, }) sort.Sort(listens) results <- models.ListensResult{Items: listens} p.Export.Elapsed = int64(i) p.Export.TotalItems += len(listens) progress <- p } p.Export.Complete() progress <- p }