+44
-4
cmd/handlers.go
+44
-4
cmd/handlers.go
···
9
9
10
10
"github.com/teal-fm/piper/db"
11
11
"github.com/teal-fm/piper/models"
12
+
atprotoauth "github.com/teal-fm/piper/oauth/atproto"
12
13
pages "github.com/teal-fm/piper/pages"
14
+
atprotoservice "github.com/teal-fm/piper/service/atproto"
13
15
"github.com/teal-fm/piper/service/musicbrainz"
16
+
"github.com/teal-fm/piper/service/playingnow"
14
17
"github.com/teal-fm/piper/service/spotify"
15
18
"github.com/teal-fm/piper/session"
16
19
)
···
187
190
188
191
func apiMusicBrainzSearch(mbService *musicbrainz.MusicBrainzService) http.HandlerFunc {
189
192
return func(w http.ResponseWriter, r *http.Request) {
193
+
if mbService == nil {
194
+
jsonResponse(w, http.StatusServiceUnavailable, map[string]string{"error": "MusicBrainz service is not available"})
195
+
return
196
+
}
190
197
191
198
params := musicbrainz.SearchParams{
192
199
Track: r.URL.Query().Get("track"),
···
318
325
}
319
326
320
327
// apiSubmitListensHandler handles ListenBrainz-compatible submissions
321
-
func apiSubmitListensHandler(database *db.DB) http.HandlerFunc {
328
+
func apiSubmitListensHandler(database *db.DB, atprotoService *atprotoauth.ATprotoAuthService, playingNowService *playingnow.PlayingNowService, mbService *musicbrainz.MusicBrainzService) http.HandlerFunc {
322
329
return func(w http.ResponseWriter, r *http.Request) {
323
330
userID, authenticated := session.GetUserID(r.Context())
324
331
if !authenticated {
···
358
365
return
359
366
}
360
367
368
+
// Get user for PDS submission
369
+
user, err := database.GetUserByID(userID)
370
+
if err != nil {
371
+
log.Printf("apiSubmitListensHandler: Error getting user %d: %v", userID, err)
372
+
jsonResponse(w, http.StatusInternalServerError, map[string]string{"error": "Failed to get user"})
373
+
return
374
+
}
375
+
361
376
// Process each listen in the payload
362
377
var processedTracks []models.Track
363
378
var errors []string
···
376
391
// Convert to internal Track format
377
392
track := listen.ConvertToTrack(userID)
378
393
379
-
// For 'playing_now' type, we might want to handle differently
380
-
// For now, treat all the same but could add temporary storage later
394
+
// Attempt to hydrate with MusicBrainz data if service is available and track doesn't have MBIDs
395
+
if mbService != nil && track.RecordingMBID == nil {
396
+
hydratedTrack, err := musicbrainz.HydrateTrack(mbService, track)
397
+
if err != nil {
398
+
log.Printf("apiSubmitListensHandler: Could not hydrate track with MusicBrainz for user %d: %v (continuing with original data)", userID, err)
399
+
// Continue with non-hydrated track
400
+
} else if hydratedTrack != nil {
401
+
track = *hydratedTrack
402
+
log.Printf("apiSubmitListensHandler: Successfully hydrated track '%s' with MusicBrainz data", track.Name)
403
+
}
404
+
}
405
+
406
+
// For 'playing_now' type, publish to PDS as actor status
381
407
if submission.ListenType == "playing_now" {
382
408
log.Printf("Received playing_now listen for user %d: %s - %s", userID, track.Artist[0].Name, track.Name)
383
-
// Could store in a separate playing_now table or just log
409
+
410
+
if user.ATProtoDID != nil && playingNowService != nil {
411
+
if err := playingNowService.PublishPlayingNow(r.Context(), userID, &track); err != nil {
412
+
log.Printf("apiSubmitListensHandler: Error publishing playing_now to PDS for user %d: %v", userID, err)
413
+
// Don't fail the request, just log the error
414
+
}
415
+
}
384
416
continue
385
417
}
386
418
···
389
421
log.Printf("apiSubmitListensHandler: Error saving track for user %d: %v", userID, err)
390
422
errors = append(errors, fmt.Sprintf("payload[%d]: failed to save track", i))
391
423
continue
424
+
}
425
+
426
+
// Submit to PDS as feed.play record
427
+
if user.ATProtoDID != nil && atprotoService != nil {
428
+
if err := atprotoservice.SubmitPlayToPDS(r.Context(), *user.ATProtoDID, &track, atprotoService); err != nil {
429
+
log.Printf("apiSubmitListensHandler: Error submitting play to PDS for user %d: %v", userID, err)
430
+
// Don't fail the request, just log the error
431
+
}
392
432
}
393
433
394
434
processedTracks = append(processedTracks, track)
+85
-6
cmd/listenbrainz_test.go
+85
-6
cmd/listenbrainz_test.go
···
11
11
12
12
"github.com/teal-fm/piper/db"
13
13
"github.com/teal-fm/piper/models"
14
+
"github.com/teal-fm/piper/service/musicbrainz"
14
15
"github.com/teal-fm/piper/session"
15
16
)
16
17
···
102
103
rr := httptest.NewRecorder()
103
104
104
105
// Call handler
105
-
handler := apiSubmitListensHandler(database)
106
+
handler := apiSubmitListensHandler(database, nil, nil, nil)
106
107
handler(rr, req)
107
108
108
109
// Check response
···
186
187
req = req.WithContext(ctx)
187
188
188
189
rr := httptest.NewRecorder()
189
-
handler := apiSubmitListensHandler(database)
190
+
handler := apiSubmitListensHandler(database, nil, nil, nil)
190
191
handler(rr, req)
191
192
192
193
if rr.Code != http.StatusOK {
···
263
264
req = req.WithContext(ctx)
264
265
265
266
rr := httptest.NewRecorder()
266
-
handler := apiSubmitListensHandler(database)
267
+
handler := apiSubmitListensHandler(database, nil, nil, nil)
267
268
handler(rr, req)
268
269
269
270
if rr.Code != http.StatusOK {
···
324
325
req = req.WithContext(ctx)
325
326
326
327
rr := httptest.NewRecorder()
327
-
handler := apiSubmitListensHandler(database)
328
+
handler := apiSubmitListensHandler(database, nil, nil, nil)
328
329
handler(rr, req)
329
330
330
331
if rr.Code != http.StatusOK {
···
419
420
req = req.WithContext(ctx)
420
421
421
422
rr := httptest.NewRecorder()
422
-
handler := apiSubmitListensHandler(database)
423
+
handler := apiSubmitListensHandler(database, nil, nil, nil)
423
424
handler(rr, req)
424
425
425
426
if rr.Code != tc.expectedStatus {
···
462
463
// No Authorization header
463
464
464
465
rr := httptest.NewRecorder()
465
-
handler := apiSubmitListensHandler(database)
466
+
handler := apiSubmitListensHandler(database, nil, nil, nil)
466
467
handler(rr, req)
467
468
468
469
if rr.Code != http.StatusUnauthorized {
···
537
538
t.Errorf("Second artist MBID not set correctly")
538
539
}
539
540
}
541
+
542
+
func TestListenBrainzSubmission_WithMusicBrainzHydration(t *testing.T) {
543
+
database := setupTestDB(t)
544
+
defer database.Close()
545
+
546
+
userID, apiKey := createTestUser(t, database)
547
+
548
+
// Create a MusicBrainz service for hydration
549
+
mbService := musicbrainz.NewMusicBrainzService(database)
550
+
551
+
// Create minimal submission (artist and track name only)
552
+
submission := models.ListenBrainzSubmission{
553
+
ListenType: "single",
554
+
Payload: []models.ListenBrainzPayload{
555
+
{
556
+
ListenedAt: func() *int64 { i := int64(1704067200); return &i }(),
557
+
TrackMetadata: models.ListenBrainzTrackMetadata{
558
+
ArtistName: "Daft Punk",
559
+
TrackName: "One More Time",
560
+
// No MBIDs provided - should be hydrated
561
+
},
562
+
},
563
+
},
564
+
}
565
+
566
+
jsonData, err := json.Marshal(submission)
567
+
if err != nil {
568
+
t.Fatalf("Failed to marshal submission: %v", err)
569
+
}
570
+
571
+
req := httptest.NewRequest(http.MethodPost, "/1/submit-listens", bytes.NewReader(jsonData))
572
+
req.Header.Set("Content-Type", "application/json")
573
+
req.Header.Set("Authorization", "Token "+apiKey)
574
+
575
+
ctx := withUserContext(req.Context(), userID)
576
+
req = req.WithContext(ctx)
577
+
578
+
rr := httptest.NewRecorder()
579
+
580
+
// Call handler with MusicBrainz service
581
+
handler := apiSubmitListensHandler(database, nil, nil, mbService)
582
+
handler(rr, req)
583
+
584
+
if rr.Code != http.StatusOK {
585
+
t.Errorf("Expected status %d, got %d. Body: %s", http.StatusOK, rr.Code, rr.Body.String())
586
+
}
587
+
588
+
// Verify track was saved
589
+
tracks, err := database.GetRecentTracks(userID, 10)
590
+
if err != nil {
591
+
t.Fatalf("Failed to get tracks from database: %v", err)
592
+
}
593
+
594
+
if len(tracks) != 1 {
595
+
t.Fatalf("Expected 1 track in database, got %d", len(tracks))
596
+
}
597
+
598
+
track := tracks[0]
599
+
600
+
// The track should have been hydrated with MusicBrainz data
601
+
// Note: This test requires network access to MusicBrainz API
602
+
// In a real test environment, you might want to mock the HTTP client
603
+
if track.RecordingMBID != nil {
604
+
t.Logf("Track was hydrated with recording MBID: %s", *track.RecordingMBID)
605
+
}
606
+
607
+
if track.ReleaseMBID != nil {
608
+
t.Logf("Track was hydrated with release MBID: %s", *track.ReleaseMBID)
609
+
}
610
+
611
+
// Even if hydration fails, the track should still be saved with original data
612
+
if track.Name != "One More Time" {
613
+
t.Errorf("Expected track name 'One More Time', got %s", track.Name)
614
+
}
615
+
if len(track.Artist) == 0 || track.Artist[0].Name != "Daft Punk" {
616
+
t.Errorf("Expected artist 'Daft Punk', got %v", track.Artist)
617
+
}
618
+
}
+1
-1
cmd/routes.go
+1
-1
cmd/routes.go
···
40
40
mux.HandleFunc("/api/v1/musicbrainz/search", apiMusicBrainzSearch(app.mbService)) // MusicBrainz (public?)
41
41
42
42
// ListenBrainz-compatible endpoint
43
-
mux.HandleFunc("/1/submit-listens", session.WithAPIAuth(apiSubmitListensHandler(app.database), app.sessionManager))
43
+
mux.HandleFunc("/1/submit-listens", session.WithAPIAuth(apiSubmitListensHandler(app.database, app.atprotoService, app.playingNowService, app.mbService), app.sessionManager))
44
44
45
45
serverUrlRoot := viper.GetString("server.root_url")
46
46
atpClientId := viper.GetString("atproto.client_id")
+136
service/atproto/submission.go
+136
service/atproto/submission.go
···
1
+
package atproto
2
+
3
+
import (
4
+
"context"
5
+
"fmt"
6
+
"log"
7
+
"time"
8
+
9
+
"github.com/bluesky-social/indigo/api/atproto"
10
+
lexutil "github.com/bluesky-social/indigo/lex/util"
11
+
"github.com/bluesky-social/indigo/xrpc"
12
+
"github.com/spf13/viper"
13
+
"github.com/teal-fm/piper/api/teal"
14
+
"github.com/teal-fm/piper/db"
15
+
"github.com/teal-fm/piper/models"
16
+
atprotoauth "github.com/teal-fm/piper/oauth/atproto"
17
+
)
18
+
19
+
// SubmitPlayToPDS submits a track play to the ATProto PDS as a feed.play record
20
+
func SubmitPlayToPDS(ctx context.Context, did string, track *models.Track, atprotoService *atprotoauth.ATprotoAuthService) error {
21
+
if did == "" {
22
+
return fmt.Errorf("DID cannot be empty")
23
+
}
24
+
25
+
// Get ATProto client
26
+
client, err := atprotoService.GetATProtoClient()
27
+
if err != nil || client == nil {
28
+
return fmt.Errorf("failed to get ATProto client: %w", err)
29
+
}
30
+
31
+
xrpcClient := atprotoService.GetXrpcClient()
32
+
if xrpcClient == nil {
33
+
return fmt.Errorf("xrpc client is not available")
34
+
}
35
+
36
+
// Get user session
37
+
sess, err := atprotoService.DB.GetAtprotoSession(did, ctx, *client)
38
+
if err != nil {
39
+
return fmt.Errorf("couldn't get Atproto session for DID %s: %w", did, err)
40
+
}
41
+
42
+
// Convert track to feed.play record
43
+
playRecord, err := TrackToPlayRecord(track)
44
+
if err != nil {
45
+
return fmt.Errorf("failed to convert track to play record: %w", err)
46
+
}
47
+
48
+
// Create the record
49
+
input := atproto.RepoCreateRecord_Input{
50
+
Collection: "fm.teal.alpha.feed.play",
51
+
Repo: sess.DID,
52
+
Record: &lexutil.LexiconTypeDecoder{Val: playRecord},
53
+
}
54
+
55
+
authArgs := db.AtpSessionToAuthArgs(sess)
56
+
var out atproto.RepoCreateRecord_Output
57
+
if err := xrpcClient.Do(ctx, authArgs, xrpc.Procedure, "application/json", "com.atproto.repo.createRecord", nil, input, &out); err != nil {
58
+
return fmt.Errorf("failed to create play record for DID %s: %w", did, err)
59
+
}
60
+
61
+
log.Printf("Successfully submitted play to PDS for DID %s: %s - %s", did, track.Artist[0].Name, track.Name)
62
+
return nil
63
+
}
64
+
65
+
// TrackToPlayRecord converts a models.Track to teal.AlphaFeedPlay
66
+
func TrackToPlayRecord(track *models.Track) (*teal.AlphaFeedPlay, error) {
67
+
if track.Name == "" {
68
+
return nil, fmt.Errorf("track name cannot be empty")
69
+
}
70
+
71
+
// Convert artists
72
+
artists := make([]*teal.AlphaFeedDefs_Artist, 0, len(track.Artist))
73
+
for _, a := range track.Artist {
74
+
artist := &teal.AlphaFeedDefs_Artist{
75
+
ArtistName: a.Name,
76
+
ArtistMbId: a.MBID,
77
+
}
78
+
artists = append(artists, artist)
79
+
}
80
+
81
+
// Prepare optional fields
82
+
var durationPtr *int64
83
+
if track.DurationMs > 0 {
84
+
durationSeconds := track.DurationMs / 1000
85
+
durationPtr = &durationSeconds
86
+
}
87
+
88
+
var playedTimeStr *string
89
+
if !track.Timestamp.IsZero() {
90
+
timeStr := track.Timestamp.Format(time.RFC3339)
91
+
playedTimeStr = &timeStr
92
+
}
93
+
94
+
var isrcPtr *string
95
+
if track.ISRC != "" {
96
+
isrcPtr = &track.ISRC
97
+
}
98
+
99
+
var originUrlPtr *string
100
+
if track.URL != "" {
101
+
originUrlPtr = &track.URL
102
+
}
103
+
104
+
var servicePtr *string
105
+
if track.ServiceBaseUrl != "" {
106
+
servicePtr = &track.ServiceBaseUrl
107
+
}
108
+
109
+
var releaseNamePtr *string
110
+
if track.Album != "" {
111
+
releaseNamePtr = &track.Album
112
+
}
113
+
114
+
// Get submission client agent
115
+
submissionAgent := viper.GetString("app.submission_agent")
116
+
if submissionAgent == "" {
117
+
submissionAgent = "piper/v0.0.1"
118
+
}
119
+
120
+
playRecord := &teal.AlphaFeedPlay{
121
+
LexiconTypeID: "fm.teal.alpha.feed.play",
122
+
TrackName: track.Name,
123
+
Artists: artists,
124
+
Duration: durationPtr,
125
+
PlayedTime: playedTimeStr,
126
+
RecordingMbId: track.RecordingMBID,
127
+
ReleaseMbId: track.ReleaseMBID,
128
+
ReleaseName: releaseNamePtr,
129
+
Isrc: isrcPtr,
130
+
OriginUrl: originUrlPtr,
131
+
MusicServiceBaseDomain: servicePtr,
132
+
SubmissionClientAgent: &submissionAgent,
133
+
}
134
+
135
+
return playRecord, nil
136
+
}
+3
-77
service/lastfm/lastfm.go
+3
-77
service/lastfm/lastfm.go
···
3
3
import (
4
4
"context"
5
5
"encoding/json"
6
-
"errors"
7
6
"fmt"
8
7
"io"
9
8
"log"
···
14
13
"sync"
15
14
"time"
16
15
17
-
"github.com/bluesky-social/indigo/api/atproto"
18
-
lexutil "github.com/bluesky-social/indigo/lex/util"
19
-
"github.com/bluesky-social/indigo/xrpc"
20
-
"github.com/spf13/viper"
21
-
"github.com/teal-fm/piper/api/teal"
22
16
"github.com/teal-fm/piper/db"
23
17
"github.com/teal-fm/piper/models"
24
18
atprotoauth "github.com/teal-fm/piper/oauth/atproto"
19
+
atprotoservice "github.com/teal-fm/piper/service/atproto"
25
20
"github.com/teal-fm/piper/service/musicbrainz"
26
21
"golang.org/x/time/rate"
27
22
)
···
419
414
}
420
415
421
416
func (l *LastFMService) SubmitTrackToPDS(did string, track *models.Track, ctx context.Context) error {
422
-
client, err := l.atprotoService.GetATProtoClient()
423
-
if err != nil || client == nil {
424
-
return err
425
-
}
426
-
427
-
xrpcClient := l.atprotoService.GetXrpcClient()
428
-
if xrpcClient == nil {
429
-
return errors.New("xrpc client is kil")
430
-
}
431
-
432
-
// we check for client above
433
-
sess, err := l.db.GetAtprotoSession(did, ctx, *client)
434
-
if err != nil {
435
-
return fmt.Errorf("Couldn't get Atproto session: %s", err)
436
-
}
437
-
438
-
// printout the session details
439
-
l.logger.Printf("Submitting track for the did: %+v\n", sess.DID)
440
-
441
-
artists := make([]*teal.AlphaFeedDefs_Artist, 0, len(track.Artist))
442
-
for _, a := range track.Artist {
443
-
artist := &teal.AlphaFeedDefs_Artist{
444
-
ArtistName: a.Name,
445
-
ArtistMbId: a.MBID,
446
-
}
447
-
artists = append(artists, artist)
448
-
}
449
-
450
-
var durationPtr *int64
451
-
if track.DurationMs > 0 {
452
-
durationSeconds := track.DurationMs / 1000
453
-
durationPtr = &durationSeconds
454
-
}
455
-
456
-
playedTimeStr := track.Timestamp.Format(time.RFC3339)
457
-
submissionAgent := viper.GetString("app.submission_agent")
458
-
if submissionAgent == "" {
459
-
submissionAgent = "piper/v0.0.1" // Default if not configured
460
-
}
461
-
462
-
// track -> tealfm track
463
-
tfmTrack := teal.AlphaFeedPlay{
464
-
LexiconTypeID: "fm.teal.alpha.feed.play", // Assuming this is the correct Lexicon ID
465
-
// tfm specifies duration in seconds
466
-
Duration: durationPtr, // Pointer required
467
-
TrackName: track.Name,
468
-
// should be unix timestamp
469
-
PlayedTime: &playedTimeStr, // Pointer required
470
-
Artists: artists,
471
-
ReleaseMbId: track.ReleaseMBID, // Pointer required
472
-
ReleaseName: &track.Album, // Pointer required
473
-
RecordingMbId: track.RecordingMBID, // Pointer required
474
-
SubmissionClientAgent: &submissionAgent, // Pointer required
475
-
}
476
-
477
-
input := atproto.RepoCreateRecord_Input{
478
-
Collection: "fm.teal.alpha.feed.play",
479
-
Repo: sess.DID,
480
-
Record: &lexutil.LexiconTypeDecoder{Val: &tfmTrack},
481
-
}
482
-
483
-
authArgs := db.AtpSessionToAuthArgs(sess)
484
-
485
-
var out atproto.RepoCreateRecord_Output
486
-
if err := xrpcClient.Do(ctx, authArgs, xrpc.Procedure, "application/json", "com.atproto.repo.createRecord", nil, input, &out); err != nil {
487
-
return err
488
-
}
489
-
490
-
// submit track to PDS
491
-
492
-
return nil
417
+
// Use shared atproto service for submission
418
+
return atprotoservice.SubmitPlayToPDS(ctx, did, track, l.atprotoService)
493
419
}
494
420
495
421
// convertLastFMTrackToModelsTrack converts a Last.fm Track to models.Track format
+7
-74
service/spotify/spotify.go
+7
-74
service/spotify/spotify.go
···
16
16
17
17
"context" // Added for context.Context
18
18
19
-
"github.com/bluesky-social/indigo/api/atproto" // Added for atproto.RepoCreateRecord_Input
20
-
lexutil "github.com/bluesky-social/indigo/lex/util" // Added for lexutil.LexiconTypeDecoder
21
-
"github.com/bluesky-social/indigo/xrpc" // Added for xrpc.Client
22
-
"github.com/spf13/viper"
23
-
"github.com/teal-fm/piper/api/teal" // Added for teal.AlphaFeedPlay
19
+
// Added for atproto.RepoCreateRecord_Input
20
+
// Added for lexutil.LexiconTypeDecoder
21
+
// Added for xrpc.Client
22
+
"github.com/spf13/viper" // Added for teal.AlphaFeedPlay
24
23
"github.com/teal-fm/piper/db"
25
24
"github.com/teal-fm/piper/models"
26
25
atprotoauth "github.com/teal-fm/piper/oauth/atproto"
26
+
atprotoservice "github.com/teal-fm/piper/service/atproto"
27
27
"github.com/teal-fm/piper/service/musicbrainz"
28
28
"github.com/teal-fm/piper/session"
29
29
)
···
60
60
}
61
61
62
62
func (s *SpotifyService) SubmitTrackToPDS(did string, track *models.Track, ctx context.Context) error {
63
-
client, err := s.atprotoService.GetATProtoClient()
64
-
if err != nil || client == nil {
65
-
s.logger.Printf("Error getting ATProto client: %v", err)
66
-
return fmt.Errorf("failed to get ATProto client: %w", err)
67
-
}
68
-
69
-
xrpcClient := s.atprotoService.GetXrpcClient()
70
-
if xrpcClient == nil {
71
-
return errors.New("xrpc client is not available")
72
-
}
73
-
74
-
sess, err := s.DB.GetAtprotoSession(did, ctx, *client)
75
-
if err != nil {
76
-
return fmt.Errorf("couldn't get Atproto session for DID %s: %w", did, err)
77
-
}
78
-
79
-
artists := make([]*teal.AlphaFeedDefs_Artist, 0, len(track.Artist))
80
-
for _, a := range track.Artist {
81
-
artist := &teal.AlphaFeedDefs_Artist{
82
-
ArtistName: a.Name,
83
-
ArtistMbId: a.MBID,
84
-
}
85
-
artists = append(artists, artist)
86
-
}
87
-
88
-
var durationPtr *int64
89
-
if track.DurationMs > 0 {
90
-
durationSeconds := track.DurationMs / 1000
91
-
durationPtr = &durationSeconds
92
-
}
93
-
94
-
playedTimeStr := track.Timestamp.Format(time.RFC3339)
95
-
submissionAgent := viper.GetString("app.submission_agent")
96
-
if submissionAgent == "" {
97
-
submissionAgent = "piper/v0.0.1" // Default if not configured
98
-
}
99
-
100
63
//Had a empty feed.play get submitted not sure why. Tracking here
101
64
if track.Name == "" {
102
65
s.logger.Println("Track name is empty. Skipping submission. Please record the logs before and send to the teal.fm Discord")
103
66
return nil
104
67
}
105
68
106
-
tfmTrack := teal.AlphaFeedPlay{
107
-
LexiconTypeID: "fm.teal.alpha.feed.play",
108
-
Duration: durationPtr,
109
-
TrackName: track.Name,
110
-
PlayedTime: &playedTimeStr,
111
-
Artists: artists,
112
-
ReleaseMbId: track.ReleaseMBID,
113
-
ReleaseName: &track.Album,
114
-
RecordingMbId: track.RecordingMBID,
115
-
// Optional: Spotify specific data if your lexicon supports it
116
-
// SpotifyTrackID: &track.ServiceID,
117
-
// SpotifyAlbumID: &track.ServiceAlbumID,
118
-
// SpotifyArtistIDs: track.ServiceArtistIDs, // Assuming this is a []string
119
-
SubmissionClientAgent: &submissionAgent,
120
-
}
121
-
122
-
input := atproto.RepoCreateRecord_Input{
123
-
Collection: "fm.teal.alpha.feed.play", // Ensure this collection is correct
124
-
Repo: sess.DID,
125
-
Record: &lexutil.LexiconTypeDecoder{Val: &tfmTrack},
126
-
}
127
-
128
-
authArgs := db.AtpSessionToAuthArgs(sess)
129
-
130
-
var out atproto.RepoCreateRecord_Output
131
-
if err := xrpcClient.Do(ctx, authArgs, xrpc.Procedure, "application/json", "com.atproto.repo.createRecord", nil, input, &out); err != nil {
132
-
s.logger.Printf("Error creating record for DID %s: %v. Input: %+v", did, err, input)
133
-
return fmt.Errorf("failed to create record on PDS for DID %s: %w", did, err)
134
-
}
135
-
136
-
s.logger.Printf("Successfully submitted track '%s' to PDS for DID %s. Record URI: %s", track.Name, did, out.Uri)
137
-
return nil
69
+
// Use shared atproto service for submission
70
+
return atprotoservice.SubmitPlayToPDS(ctx, did, track, s.atprotoService)
138
71
}
139
72
140
73
func (s *SpotifyService) SetAccessToken(token string, refreshToken string, userId int64, hasSession bool) (int64, error) {