[WIP] music platform user data scraper
teal-fm atproto
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263package main
import ( "context" "encoding/json" "fmt" "log" "net/http" "strings" "time"
"github.com/spf13/viper" "golang.org/x/oauth2" spotifyOauth "golang.org/x/oauth2/spotify"
"github.com/teal-fm/piper/config" "github.com/teal-fm/piper/db" "github.com/teal-fm/piper/oauth" "github.com/teal-fm/piper/oauth/atproto" "github.com/teal-fm/piper/pages" apikeyService "github.com/teal-fm/piper/service/apikey" "github.com/teal-fm/piper/service/applemusic" "github.com/teal-fm/piper/service/lastfm" "github.com/teal-fm/piper/service/musicbrainz" "github.com/teal-fm/piper/service/playingnow" "github.com/teal-fm/piper/service/spotify" "github.com/teal-fm/piper/session")
type application struct { database *db.DB sessionManager *session.Manager oauthManager *oauth.ServiceManager spotifyService *spotify.Service lastfmService *lastfm.Service apiKeyService *apikeyService.Service mbService *musicbrainz.Service atprotoService *atproto.AuthService playingNowService *playingnow.Service appleMusicService *applemusic.Service pages *pages.Pages buildTime time.Time}
// JSON API handlers
func jsonResponse(w http.ResponseWriter, statusCode int, data any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(statusCode) if data != nil { err := json.NewEncoder(w).Encode(data) if err != nil { log.Printf("Error encoding JSON response: %v", err) return } }}
func main() { config.Load()
database, err := db.New(viper.GetString("db.path")) if err != nil { log.Fatalf("Error connecting to database: %v", err) }
if err := database.Initialize(); err != nil { log.Fatalf("Error initializing database: %v", err) }
sessionManager := session.NewSessionManager(database)
// --- Service Initializations ---
var newJwkPrivateKey = viper.GetString("atproto.client_secret_key") if newJwkPrivateKey == "" { fmt.Printf("You now have to set the ATPROTO_CLIENT_SECRET_KEY env var to a private key. This can be done via goat key generate -t P-256") return } var clientSecretKeyId = viper.GetString("atproto.client_secret_key_id") if clientSecretKeyId == "" { fmt.Printf("You also now have to set the ATPROTO_CLIENT_SECRET_KEY_ID env var to a key ID. This needs to be persistent and unique. Here's one for you: %d", time.Now().Unix()) return }
var allowedDids = viper.GetStringSlice("allowed_dids") if len(allowedDids) > 0 { log.Printf("Allowed DIDs provided. Only allowing %s\n", strings.Join(allowedDids, ", ")) }
atprotoService, err := atproto.NewATprotoAuthService( database, sessionManager, newJwkPrivateKey, viper.GetString("atproto.client_id"), viper.GetString("atproto.callback_url"), clientSecretKeyId, allowedDids, ) if err != nil { log.Fatalf("Error creating ATproto auth service: %v", err) }
mbService := musicbrainz.NewMusicBrainzService(database) playingNowService := playingnow.NewPlayingNowService(database, atprotoService, mbService)
// Check feature toggles for music services enableSpotify := viper.GetBool("enable_spotify") enableLastFM := viper.GetBool("enable_lastfm") enableAppleMusic := viper.GetBool("enable_applemusic")
var spotifyService *spotify.Service var lastfmService *lastfm.Service var appleMusicService *applemusic.Service
// Initialize Spotify service if enabled and credentials are present if enableSpotify { clientID := viper.GetString("spotify.client_id") clientSecret := viper.GetString("spotify.client_secret")
if clientID != "" && clientSecret != "" { spotifyService = spotify.NewSpotifyService(database, atprotoService, mbService, playingNowService) log.Println("Spotify service enabled and configured") } else { log.Println("Spotify enabled but credentials missing (client_id or client_secret). Spotify features will be disabled.") viper.Set("enable_spotify", false) } } else { log.Println("Spotify service disabled via ENABLE_SPOTIFY=false") }
// Initialize Last.fm service if enabled and API key is present if enableLastFM { apiKey := viper.GetString("lastfm.api_key")
if apiKey != "" { lastfmService = lastfm.NewLastFMService(database, apiKey, mbService, atprotoService, playingNowService) log.Println("Last.fm service enabled and configured") } else { log.Println("Last.fm enabled but API key missing. Last.fm features will be disabled.") viper.Set("enable_lastfm", false) } } else { log.Println("Last.fm service disabled via ENABLE_LASTFM=false") }
// Initialize Apple Music service if enabled and credentials are present if enableAppleMusic { // Read Apple Music settings with env fallbacks teamID := viper.GetString("applemusic.team_id") if teamID == "" { teamID = viper.GetString("APPLE_MUSIC_TEAM_ID") } keyID := viper.GetString("applemusic.key_id") if keyID == "" { keyID = viper.GetString("APPLE_MUSIC_KEY_ID") } keyPath := viper.GetString("applemusic.private_key_path") if keyPath == "" { keyPath = viper.GetString("APPLE_MUSIC_PRIVATE_KEY_PATH") }
// Only initialize if all required credentials are present if teamID != "" && keyID != "" && keyPath != "" { appleMusicService = applemusic.NewService( teamID, keyID, keyPath, ).WithPersistence( func() (string, time.Time, bool, error) { return database.GetAppleMusicDeveloperToken() }, func(token string, exp time.Time) error { return database.SaveAppleMusicDeveloperToken(token, exp) }, ).WithDeps(database, atprotoService, mbService, playingNowService) log.Println("Apple Music service enabled and configured") } else { log.Println("Apple Music enabled but credentials missing (team_id, key_id, or private_key_path). Apple Music features will be disabled.") viper.Set("enable_applemusic", false) } } else { log.Println("Apple Music service disabled via ENABLE_APPLEMUSIC=false") }
oauthManager := oauth.NewOAuthServiceManager()
// Register Spotify OAuth service only if Spotify is enabled and configured if spotifyService != nil { spotifyOAuth := oauth.NewOAuth2Service( oauth2.Config{ ClientID: viper.GetString("spotify.client_id"), ClientSecret: viper.GetString("spotify.client_secret"), RedirectURL: viper.GetString("callback.spotify"), Scopes: viper.GetStringSlice("spotify.scopes"), Endpoint: spotifyOauth.Endpoint, }, spotifyService, log.Default(), func(ctx context.Context) int64 { id, _ := session.GetUserID(ctx) return id }, ) oauthManager.RegisterService("spotify", spotifyOAuth) log.Println("Spotify OAuth service registered") }
oauthManager.RegisterService("atproto", atprotoService)
apiKeyService := apikeyService.NewAPIKeyService(database, sessionManager)
app := &application{ database: database, sessionManager: sessionManager, oauthManager: oauthManager, apiKeyService: apiKeyService, mbService: mbService, spotifyService: spotifyService, lastfmService: lastfmService, atprotoService: atprotoService, playingNowService: playingNowService, appleMusicService: appleMusicService, pages: pages.NewPages(), buildTime: resolveBuildTime(), }
trackerInterval := time.Duration(viper.GetInt("tracker.interval")) * time.Second
// Start Spotify listening tracker if service is configured if spotifyService != nil { go spotifyService.StartListeningTracker(trackerInterval) log.Println("Spotify listening tracker started") }
// Start Last.fm listening tracker if service is configured if lastfmService != nil { lastfmInterval := time.Duration(viper.GetInt("lastfm.interval_seconds")) * time.Second if lastfmInterval <= 0 { lastfmInterval = 30 * time.Second } go lastfmService.StartListeningTracker(lastfmInterval) log.Println("Last.fm listening tracker started") }
// Start Apple Music tracker if service is configured if appleMusicService != nil { go appleMusicService.StartListeningTracker(trackerInterval) log.Println("Apple Music listening tracker started") }
serverAddr := fmt.Sprintf("%s:%s", viper.GetString("server.host"), viper.GetString("server.port")) server := &http.Server{ Addr: serverAddr, Handler: app.routes(), IdleTimeout: time.Minute, ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, } fmt.Printf("Server running at: http://%s\n", serverAddr) log.Fatal(server.ListenAndServe())}