Skip to content

Expand SoundCloud downloading functionality#820

Draft
Electr0Fi wants to merge 1 commit intomxpv:mainfrom
Electr0Fi:testing
Draft

Expand SoundCloud downloading functionality#820
Electr0Fi wants to merge 1 commit intomxpv:mainfrom
Electr0Fi:testing

Conversation

@Electr0Fi
Copy link

@Electr0Fi Electr0Fi commented Feb 6, 2026

All of the functionality below has been "Vibe Coded" using ChatGPT.

  • Modifed pkg/builder/url.go to recognize and accept SoundCloud user profile URLs (e.g., https://soundcloud.com/username) as valid feed sources
  • Distinguishes between playlist URLs (/sets/) and user profile URLs (just /)
  • Sets appropriate Feed.LinkType (likely TypeUser) for profile URLs
  • Implemented SoundCloud API integration to resolve usernames to user IDs and fetch user track lists
  • Converts fetched tracks into Podsync episode entries (title, audio URL, duration, cover art, publication date)
  • Extended existing playlist handling logic to work with user profile feeds
  • Added configuration support for SoundCloud API client ID under [tokens] in config and PODSYNC_SOUNDCLOUD_CLIENT_ID environment variable
  • Ensured the solution works with the existing soundcloud-api Go library's client ID scraping mechanism

NOTE: Requires testing before merge!

@Electr0Fi Electr0Fi marked this pull request as draft February 6, 2026 23:15
@mxpv mxpv requested a review from Copilot March 7, 2026 21:53
if !ok && provider == model.ProviderSoundcloud {
val, ok = os.LookupEnv("PODSYNC_SOUNDCLOUD_API_KEY")
if ok {
envVar = "PODSYNC_SOUNDCLOUD_API_KEY"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you pls output warning? That using deprecated key which will be removed in future versions

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands SoundCloud support in Podsync to allow fetching feeds from SoundCloud user profiles (uploads) in addition to the previously supported playlist URLs. It refactors the SoundCloud URL parser, builder, and configuration to handle both URL types, makes the SoundCloud API key optional (since the underlying library can auto-scrape a client_id), and adds backward compatibility for the renamed environment variable.

Changes:

  • Extended parseSoundcloudURL to recognize user profile URLs (/username, /username/tracks) alongside playlist URLs (/username/sets/<playlist>), with reserved route filtering
  • Added buildUser method to the SoundCloud builder that resolves user profiles and fetches their tracks via the SoundCloud API v2, plus refactored existing playlist logic into buildPlaylist with a shared trackToEpisode helper
  • Made SoundCloud API key optional throughout the stack (updater, config, builder factory) and renamed the environment variable from PODSYNC_SOUNDCLOUD_API_KEY to PODSYNC_SOUNDCLOUD_CLIENT_ID with backward compatibility

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pkg/builder/url.go Rewrote parseSoundcloudURL to support user profile and playlist URLs with reserved route filtering
pkg/builder/url_test.go Added unit tests for new SoundCloud URL parsing (playlist, user profile, invalid URLs)
pkg/builder/soundcloud.go Refactored builder into buildPlaylist/buildUser methods, added trackToEpisode helper and fetchUserTracks for API v2 integration
pkg/builder/soundcloud_test.go Updated integration tests to cover user profile feeds and relax assertions for optional fields
pkg/builder/builder.go Passes optional key parameter to NewSoundcloudBuilder
services/update/updater.go Allows missing key provider for SoundCloud
cmd/podsync/config.go Renamed env var to PODSYNC_SOUNDCLOUD_CLIENT_ID with backward compat for PODSYNC_SOUNDCLOUD_API_KEY
config.toml.example Documents optional SoundCloud client_id config
README.md Updated features list, added SoundCloud URL format docs, updated env var table, but regressed Docker run formatting

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +185 to +190
var tracks []soundcloudapi.Track
if err := json.NewDecoder(resp.Body).Decode(&tracks); err != nil {
return nil, errors.Wrap(err, "failed to decode soundcloud user tracks response")
}

return tracks, nil
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SoundCloud API v2 /users/{id}/tracks endpoint returns a paginated JSON object with a collection key containing the track array (e.g., {"collection": [...], "next_href": "..."}), not a bare JSON array. Decoding directly into []soundcloudapi.Track will fail with a JSON unmarshalling error at runtime.

You need to define a wrapper struct (e.g., struct { Collection []soundcloudapi.Track \json:"collection"` }) and decode into that, then extract the .Collection` field.

Suggested change
var tracks []soundcloudapi.Track
if err := json.NewDecoder(resp.Body).Decode(&tracks); err != nil {
return nil, errors.Wrap(err, "failed to decode soundcloud user tracks response")
}
return tracks, nil
var apiResp struct {
Collection []soundcloudapi.Track `json:"collection"`
}
if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
return nil, errors.Wrap(err, "failed to decode soundcloud user tracks response")
}
return apiResp.Collection, nil

Copilot uses AI. Check for mistakes.
-v $(pwd)/db:/app/db/ \
-v $(pwd)/config.toml:/app/config.toml \
ghcr.io/mxpv/podsync:latest
$ docker run -p 8080:8080 -v $(pwd)/data:/app/data/ -v $(pwd)/db:/app/db/ -v $(pwd)/config.toml:/app/config.toml ghcr.io/mxpv/podsync:latest
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Docker run command has been collapsed from a multi-line \-continued command into a single long line, making it much harder to read in the documentation. This appears to be an unintentional formatting regression. It should be kept as a multi-line command with backslash continuation for readability.

Suggested change
$ docker run -p 8080:8080 -v $(pwd)/data:/app/data/ -v $(pwd)/db:/app/db/ -v $(pwd)/config.toml:/app/config.toml ghcr.io/mxpv/podsync:latest
$ docker run \
-p 8080:8080 \
-v $(pwd)/data:/app/data/ \
-v $(pwd)/db:/app/db/ \
-v $(pwd)/config.toml:/app/config.toml \
ghcr.io/mxpv/podsync:latest

Copilot uses AI. Check for mistakes.
Comment on lines +93 to +95
user, err := s.client.GetUser(soundcloudapi.GetUserOptions{
ProfileURL: cfg.URL,
})
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the user provides https://soundcloud.com/username/tracks, cfg.URL is passed directly to s.client.GetUser(soundcloudapi.GetUserOptions{ProfileURL: cfg.URL}). The SoundCloud resolve API may not recognize https://soundcloud.com/username/tracks as a valid profile URL (only https://soundcloud.com/username resolves to a user). You should strip the /tracks suffix before passing the URL to GetUser, or reconstruct the profile URL from the parsed username (e.g., "https://soundcloud.com/" + info.ItemID).

Copilot uses AI. Check for mistakes.
limit := cfg.PageSize
if limit <= 0 {
// Keep a sane default; the feed can still be "unlimited" by setting PageSize high.
limit = 20
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded default of 20 is inconsistent with the project's DefaultPageSize of 50 (defined in pkg/model/defaults.go:10). Moreover, PageSize will never actually be <= 0 at this point because config.go:179-181 sets it to model.DefaultPageSize (50) if it's 0. This means the limit <= 0 branch is dead code, and the fallback to 20 would never trigger. Consider removing this dead code, or if you want to keep a safety check, use model.DefaultPageSize instead of the magic number 20 to stay consistent.

Suggested change
limit = 20
limit = model.DefaultPageSize

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants