/* Copyright © 2023 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 ( "os" "path" "path/filepath" "slices" "sort" "time" "go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" ) const historyFileGlob = "Streaming_History_Audio_*.json" type SpotifyHistoryBackend struct { dirPath string ignoreIncognito bool ignoreSkipped bool skippedMinDurationMs int } func (b *SpotifyHistoryBackend) Name() string { return "spotify-history" } func (b *SpotifyHistoryBackend) Options() []models.BackendOption { return []models.BackendOption{{ Name: "dir-path", Label: i18n.Tr("Directory path"), Type: models.String, }, { 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-ms", Label: i18n.Tr("Minimum playback duration for skipped tracks (milliseconds)"), Type: models.Int, Default: "30000", }} } func (b *SpotifyHistoryBackend) FromConfig(config *config.ServiceConfig) models.Backend { b.dirPath = config.GetString("dir-path") b.ignoreIncognito = config.GetBool("ignore-incognito", true) b.ignoreSkipped = config.GetBool("ignore-skipped", false) b.skippedMinDurationMs = config.GetInt("ignore-min-duration-ms", 30000) return b } func (b *SpotifyHistoryBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) { defer close(results) files, err := filepath.Glob(path.Join(b.dirPath, historyFileGlob)) if err != nil { progress <- models.Progress{}.Complete() results <- models.ListensResult{Error: err} return } slices.Sort(files) fileCount := int64(len(files)) p := models.Progress{Total: fileCount} for i, filePath := range files { history, err := readHistoryFile(filePath) if err != nil { progress <- models.Progress{}.Complete() results <- models.ListensResult{Error: err} return } listens := history.AsListenList(ListenListOptions{ IgnoreIncognito: b.ignoreIncognito, IgnoreSkipped: b.ignoreSkipped, SkippedMinDurationMs: b.skippedMinDurationMs, }) sort.Sort(listens) results <- models.ListensResult{Items: listens} p.Elapsed = int64(i) progress <- p } progress <- p.Complete() } func readHistoryFile(filePath string) (StreamingHistory, error) { file, err := os.Open(filePath) if err != nil { return nil, err } defer file.Close() history := StreamingHistory{} err = history.Read(file) if err != nil { return nil, err } return history, nil }