mirror of
https://git.sr.ht/~phw/scotty
synced 2025-04-29 21:27:05 +02:00
Restructured code, moved all modules into internal
For now all modules are considered internal. This might change later
This commit is contained in:
parent
f94e0f1e85
commit
857661ebf9
76 changed files with 121 additions and 68 deletions
110
internal/backends/funkwhale/client.go
Normal file
110
internal/backends/funkwhale/client.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package funkwhale
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
const MaxItemsPerGet = 50
|
||||
const DefaultRateLimitWaitSeconds = 5
|
||||
|
||||
type Client struct {
|
||||
HttpClient *resty.Client
|
||||
token string
|
||||
}
|
||||
|
||||
func NewClient(serverUrl string, token string) Client {
|
||||
client := resty.New()
|
||||
client.SetBaseURL(serverUrl)
|
||||
client.SetAuthScheme("Bearer")
|
||||
client.SetAuthToken(token)
|
||||
client.SetHeader("Accept", "application/json")
|
||||
|
||||
// Handle rate limiting (see https://docs.funkwhale.audio/developer/api/rate-limit.html)
|
||||
client.SetRetryCount(5)
|
||||
client.AddRetryCondition(
|
||||
func(r *resty.Response, err error) bool {
|
||||
code := r.StatusCode()
|
||||
return code == http.StatusTooManyRequests || code >= http.StatusInternalServerError
|
||||
},
|
||||
)
|
||||
client.SetRetryMaxWaitTime(time.Duration(1 * time.Minute))
|
||||
client.SetRetryAfter(func(client *resty.Client, resp *resty.Response) (time.Duration, error) {
|
||||
var err error
|
||||
var retryAfter int = DefaultRateLimitWaitSeconds
|
||||
if resp.StatusCode() == http.StatusTooManyRequests {
|
||||
retryAfter, err = strconv.Atoi(resp.Header().Get("Retry-After"))
|
||||
if err != nil {
|
||||
retryAfter = DefaultRateLimitWaitSeconds
|
||||
}
|
||||
}
|
||||
return time.Duration(retryAfter * int(time.Second)), err
|
||||
})
|
||||
|
||||
return Client{
|
||||
HttpClient: client,
|
||||
token: token,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Client) GetHistoryListenings(user string, page int, perPage int) (result ListeningsResult, err error) {
|
||||
const path = "/api/v1/history/listenings"
|
||||
response, err := c.HttpClient.R().
|
||||
SetQueryParams(map[string]string{
|
||||
"username": user,
|
||||
"page": strconv.Itoa(page),
|
||||
"page_size": strconv.Itoa(perPage),
|
||||
"ordering": "-creation_date",
|
||||
}).
|
||||
SetResult(&result).
|
||||
Get(path)
|
||||
|
||||
if response.StatusCode() != 200 {
|
||||
err = errors.New(response.String())
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) GetFavoriteTracks(page int, perPage int) (result FavoriteTracksResult, err error) {
|
||||
const path = "/api/v1/favorites/tracks"
|
||||
response, err := c.HttpClient.R().
|
||||
SetQueryParams(map[string]string{
|
||||
"page": strconv.Itoa(page),
|
||||
"page_size": strconv.Itoa(perPage),
|
||||
"ordering": "-creation_date",
|
||||
}).
|
||||
SetResult(&result).
|
||||
Get(path)
|
||||
|
||||
if response.StatusCode() != 200 {
|
||||
err = errors.New(response.String())
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
98
internal/backends/funkwhale/client_test.go
Normal file
98
internal/backends/funkwhale/client_test.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package funkwhale_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/jarcoal/httpmock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uploadedlobster.com/scotty/internal/backends/funkwhale"
|
||||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
serverUrl := "https://funkwhale.example.com"
|
||||
token := "foobar123"
|
||||
client := funkwhale.NewClient(serverUrl, token)
|
||||
assert.Equal(t, serverUrl, client.HttpClient.BaseURL)
|
||||
assert.Equal(t, token, client.HttpClient.Token)
|
||||
}
|
||||
|
||||
func TestGetHistoryListenings(t *testing.T) {
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
serverUrl := "https://funkwhale.example.com"
|
||||
token := "thetoken"
|
||||
client := funkwhale.NewClient(serverUrl, token)
|
||||
setupHttpMock(t, client.HttpClient.GetClient(),
|
||||
"https://funkwhale.example.com/api/v1/history/listenings",
|
||||
"testdata/listenings.json")
|
||||
|
||||
result, err := client.GetHistoryListenings("outsidecontext", 0, 2)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert := assert.New(t)
|
||||
assert.Equal(2204, result.Count)
|
||||
require.Len(t, result.Results, 2)
|
||||
listen1 := result.Results[0]
|
||||
assert.Equal("2023-11-09T23:59:29.022005Z", listen1.CreationDate)
|
||||
assert.Equal("Way to Eden", listen1.Track.Title)
|
||||
assert.Equal("Hazeshuttle", listen1.Track.Album.Title)
|
||||
assert.Equal("Hazeshuttle", listen1.Track.Artist.Name)
|
||||
assert.Equal("phw", listen1.User.UserName)
|
||||
}
|
||||
|
||||
func TestGetFavoriteTracks(t *testing.T) {
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
token := "thetoken"
|
||||
serverUrl := "https://funkwhale.example.com"
|
||||
client := funkwhale.NewClient(serverUrl, token)
|
||||
setupHttpMock(t, client.HttpClient.GetClient(),
|
||||
"https://funkwhale.example.com/api/v1/favorites/tracks",
|
||||
"testdata/favorite-tracks.json")
|
||||
|
||||
result, err := client.GetFavoriteTracks(0, 2)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert := assert.New(t)
|
||||
assert.Equal(76, result.Count)
|
||||
require.Len(t, result.Results, 2)
|
||||
fav1 := result.Results[0]
|
||||
assert.Equal("2023-11-05T20:32:32.339738Z", fav1.CreationDate)
|
||||
assert.Equal("Reign", fav1.Track.Title)
|
||||
assert.Equal("Home Economics", fav1.Track.Album.Title)
|
||||
assert.Equal("Prinzhorn Dance School", fav1.Track.Artist.Name)
|
||||
assert.Equal("phw", fav1.User.UserName)
|
||||
}
|
||||
|
||||
func setupHttpMock(t *testing.T, client *http.Client, url string, testDataPath string) {
|
||||
httpmock.ActivateNonDefault(client)
|
||||
|
||||
responder, err := httpmock.NewJsonResponder(200, httpmock.File(testDataPath))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
httpmock.RegisterResponder("GET", url, responder)
|
||||
}
|
195
internal/backends/funkwhale/funkwhale.go
Normal file
195
internal/backends/funkwhale/funkwhale.go
Normal file
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package funkwhale
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"go.uploadedlobster.com/scotty/internal/models"
|
||||
)
|
||||
|
||||
const FunkwhaleClientName = "Funkwhale"
|
||||
|
||||
type FunkwhaleApiBackend struct {
|
||||
client Client
|
||||
username string
|
||||
}
|
||||
|
||||
func (b *FunkwhaleApiBackend) Name() string { return "funkwhale" }
|
||||
|
||||
func (b *FunkwhaleApiBackend) FromConfig(config *viper.Viper) models.Backend {
|
||||
b.client = NewClient(
|
||||
config.GetString("server-url"),
|
||||
config.GetString("token"),
|
||||
)
|
||||
b.username = config.GetString("username")
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *FunkwhaleApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) {
|
||||
page := 1
|
||||
perPage := MaxItemsPerGet
|
||||
|
||||
defer close(results)
|
||||
|
||||
// We need to gather the full list of listens in order to sort them
|
||||
listens := make(models.ListensList, 0, 2*perPage)
|
||||
p := models.Progress{Total: int64(perPage)}
|
||||
|
||||
out:
|
||||
for {
|
||||
result, err := b.client.GetHistoryListenings(b.username, page, perPage)
|
||||
if err != nil {
|
||||
results <- models.ListensResult{Error: err}
|
||||
}
|
||||
|
||||
count := len(result.Results)
|
||||
if count == 0 {
|
||||
break out
|
||||
}
|
||||
|
||||
for _, fwListen := range result.Results {
|
||||
listen := fwListen.AsListen()
|
||||
if listen.ListenedAt.Unix() > oldestTimestamp.Unix() {
|
||||
p.Elapsed += 1
|
||||
listens = append(listens, listen)
|
||||
} else {
|
||||
break out
|
||||
}
|
||||
}
|
||||
|
||||
if result.Next == "" {
|
||||
// No further results
|
||||
p.Total = p.Elapsed
|
||||
p.Total -= int64(perPage - count)
|
||||
break out
|
||||
}
|
||||
|
||||
p.Total += int64(perPage)
|
||||
progress <- p
|
||||
page += 1
|
||||
}
|
||||
|
||||
sort.Sort(listens)
|
||||
progress <- p.Complete()
|
||||
results <- models.ListensResult{Listens: listens}
|
||||
}
|
||||
|
||||
func (b *FunkwhaleApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) {
|
||||
page := 1
|
||||
perPage := MaxItemsPerGet
|
||||
|
||||
defer close(results)
|
||||
|
||||
// We need to gather the full list of listens in order to sort them
|
||||
loves := make(models.LovesList, 0, 2*perPage)
|
||||
p := models.Progress{Total: int64(perPage)}
|
||||
|
||||
out:
|
||||
for {
|
||||
result, err := b.client.GetFavoriteTracks(page, perPage)
|
||||
if err != nil {
|
||||
progress <- p.Complete()
|
||||
results <- models.LovesResult{Error: err}
|
||||
return
|
||||
}
|
||||
|
||||
count := len(result.Results)
|
||||
if count == 0 {
|
||||
break out
|
||||
}
|
||||
|
||||
for _, favorite := range result.Results {
|
||||
love := favorite.AsLove()
|
||||
if love.Created.Unix() > oldestTimestamp.Unix() {
|
||||
p.Elapsed += 1
|
||||
loves = append(loves, love)
|
||||
} else {
|
||||
break out
|
||||
}
|
||||
}
|
||||
|
||||
if result.Next == "" {
|
||||
// No further results
|
||||
break out
|
||||
}
|
||||
|
||||
p.Total += int64(perPage)
|
||||
progress <- p
|
||||
page += 1
|
||||
}
|
||||
|
||||
sort.Sort(loves)
|
||||
progress <- p.Complete()
|
||||
results <- models.LovesResult{Loves: loves}
|
||||
}
|
||||
|
||||
func (l Listening) AsListen() models.Listen {
|
||||
listen := models.Listen{
|
||||
UserName: l.User.UserName,
|
||||
Track: l.Track.AsTrack(),
|
||||
}
|
||||
|
||||
listenedAt, err := time.Parse(time.RFC3339, l.CreationDate)
|
||||
if err == nil {
|
||||
listen.ListenedAt = listenedAt
|
||||
}
|
||||
|
||||
return listen
|
||||
}
|
||||
|
||||
func (f FavoriteTrack) AsLove() models.Love {
|
||||
track := f.Track.AsTrack()
|
||||
love := models.Love{
|
||||
UserName: f.User.UserName,
|
||||
RecordingMbid: track.RecordingMbid,
|
||||
Track: track,
|
||||
}
|
||||
|
||||
created, err := time.Parse(time.RFC3339, f.CreationDate)
|
||||
if err == nil {
|
||||
love.Created = created
|
||||
}
|
||||
|
||||
return love
|
||||
}
|
||||
|
||||
func (t Track) AsTrack() models.Track {
|
||||
recordingMbid := models.MBID(t.RecordingMbid)
|
||||
track := models.Track{
|
||||
TrackName: t.Title,
|
||||
ReleaseName: t.Album.Title,
|
||||
ArtistNames: []string{t.Artist.Name},
|
||||
TrackNumber: t.Position,
|
||||
DiscNumber: t.DiscNumber,
|
||||
RecordingMbid: recordingMbid,
|
||||
ReleaseMbid: models.MBID(t.Album.ReleaseMbid),
|
||||
ArtistMbids: []models.MBID{models.MBID(t.Artist.ArtistMbid)},
|
||||
Tags: t.Tags,
|
||||
AdditionalInfo: map[string]any{
|
||||
"media_player": FunkwhaleClientName,
|
||||
},
|
||||
}
|
||||
|
||||
if len(t.Uploads) > 0 {
|
||||
track.Duration = time.Duration(t.Uploads[0].Duration * int(time.Second))
|
||||
}
|
||||
|
||||
return track
|
||||
}
|
126
internal/backends/funkwhale/funkwhale_test.go
Normal file
126
internal/backends/funkwhale/funkwhale_test.go
Normal file
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package funkwhale_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uploadedlobster.com/scotty/internal/backends/funkwhale"
|
||||
"go.uploadedlobster.com/scotty/internal/models"
|
||||
)
|
||||
|
||||
func TestFromConfig(t *testing.T) {
|
||||
config := viper.New()
|
||||
config.Set("token", "thetoken")
|
||||
backend := (&funkwhale.FunkwhaleApiBackend{}).FromConfig(config)
|
||||
assert.IsType(t, &funkwhale.FunkwhaleApiBackend{}, backend)
|
||||
}
|
||||
|
||||
func TestFunkwhaleListeningAsListen(t *testing.T) {
|
||||
fwListen := funkwhale.Listening{
|
||||
CreationDate: "2023-11-09T23:59:29.022005Z",
|
||||
User: funkwhale.User{
|
||||
UserName: "outsidecontext",
|
||||
},
|
||||
Track: funkwhale.Track{
|
||||
Title: "Oweynagat",
|
||||
RecordingMbid: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12",
|
||||
Position: 5,
|
||||
DiscNumber: 1,
|
||||
Tags: []string{"foo", "bar"},
|
||||
Artist: funkwhale.Artist{
|
||||
Name: "Dool",
|
||||
ArtistMbid: "24412926-c7bd-48e8-afad-8a285b42e131",
|
||||
},
|
||||
Album: funkwhale.Album{
|
||||
Title: "Here Now, There Then",
|
||||
ReleaseMbid: "d7f22677-9803-4d21-ba42-081b633a6f68",
|
||||
},
|
||||
Uploads: []funkwhale.Upload{
|
||||
{
|
||||
Duration: 414,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
listen := fwListen.AsListen()
|
||||
assert := assert.New(t)
|
||||
assert.Equal(time.Unix(1699574369, 0).Unix(), listen.ListenedAt.Unix())
|
||||
assert.Equal(fwListen.User.UserName, listen.UserName)
|
||||
assert.Equal(time.Duration(414*time.Second), listen.Duration)
|
||||
assert.Equal(fwListen.Track.Title, listen.TrackName)
|
||||
assert.Equal(fwListen.Track.Album.Title, listen.ReleaseName)
|
||||
assert.Equal([]string{fwListen.Track.Artist.Name}, listen.ArtistNames)
|
||||
assert.Equal(fwListen.Track.Position, listen.Track.TrackNumber)
|
||||
assert.Equal(fwListen.Track.DiscNumber, listen.Track.DiscNumber)
|
||||
assert.Equal(fwListen.Track.Tags, listen.Track.Tags)
|
||||
// assert.Equal(backends.FunkwhaleClientName, listen.AdditionalInfo["disc_number"])
|
||||
assert.Equal(models.MBID(fwListen.Track.RecordingMbid), listen.RecordingMbid)
|
||||
assert.Equal(models.MBID(fwListen.Track.Album.ReleaseMbid), listen.ReleaseMbid)
|
||||
assert.Equal(models.MBID(fwListen.Track.Artist.ArtistMbid), listen.ArtistMbids[0])
|
||||
assert.Equal(funkwhale.FunkwhaleClientName, listen.AdditionalInfo["media_player"])
|
||||
}
|
||||
|
||||
func TestFunkwhaleFavoriteTrackAsLove(t *testing.T) {
|
||||
favorite := funkwhale.FavoriteTrack{
|
||||
CreationDate: "2023-11-09T23:59:29.022005Z",
|
||||
User: funkwhale.User{
|
||||
UserName: "outsidecontext",
|
||||
},
|
||||
Track: funkwhale.Track{
|
||||
Title: "Oweynagat",
|
||||
RecordingMbid: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12",
|
||||
Position: 5,
|
||||
DiscNumber: 1,
|
||||
Tags: []string{"foo", "bar"},
|
||||
Artist: funkwhale.Artist{
|
||||
Name: "Dool",
|
||||
ArtistMbid: "24412926-c7bd-48e8-afad-8a285b42e131",
|
||||
},
|
||||
Album: funkwhale.Album{
|
||||
Title: "Here Now, There Then",
|
||||
ReleaseMbid: "d7f22677-9803-4d21-ba42-081b633a6f68",
|
||||
},
|
||||
Uploads: []funkwhale.Upload{
|
||||
{
|
||||
Duration: 414,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
love := favorite.AsLove()
|
||||
assert := assert.New(t)
|
||||
assert.Equal(time.Unix(1699574369, 0).Unix(), love.Created.Unix())
|
||||
assert.Equal(favorite.User.UserName, love.UserName)
|
||||
assert.Equal(time.Duration(414*time.Second), love.Duration)
|
||||
assert.Equal(favorite.Track.Title, love.TrackName)
|
||||
assert.Equal(favorite.Track.Album.Title, love.ReleaseName)
|
||||
assert.Equal([]string{favorite.Track.Artist.Name}, love.ArtistNames)
|
||||
assert.Equal(favorite.Track.Position, love.Track.TrackNumber)
|
||||
assert.Equal(favorite.Track.DiscNumber, love.Track.DiscNumber)
|
||||
assert.Equal(favorite.Track.Tags, love.Track.Tags)
|
||||
assert.Equal(models.MBID(favorite.Track.RecordingMbid), love.RecordingMbid)
|
||||
assert.Equal(models.MBID(favorite.Track.RecordingMbid), love.Track.RecordingMbid)
|
||||
assert.Equal(models.MBID(favorite.Track.Album.ReleaseMbid), love.ReleaseMbid)
|
||||
require.Len(t, love.Track.ArtistMbids, 1)
|
||||
assert.Equal(models.MBID(favorite.Track.Artist.ArtistMbid), love.ArtistMbids[0])
|
||||
assert.Equal(funkwhale.FunkwhaleClientName, love.AdditionalInfo["media_player"])
|
||||
}
|
87
internal/backends/funkwhale/models.go
Normal file
87
internal/backends/funkwhale/models.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package funkwhale
|
||||
|
||||
type ListeningsResult struct {
|
||||
Count int `json:"count"`
|
||||
Previous string `json:"previous"`
|
||||
Next string `json:"next"`
|
||||
Results []Listening `json:"results"`
|
||||
}
|
||||
|
||||
type Listening struct {
|
||||
Id int `json:"int"`
|
||||
User User `json:"user"`
|
||||
Track Track `json:"track"`
|
||||
CreationDate string `json:"creation_date"`
|
||||
}
|
||||
|
||||
type FavoriteTracksResult struct {
|
||||
Count int `json:"count"`
|
||||
Previous string `json:"previous"`
|
||||
Next string `json:"next"`
|
||||
Results []FavoriteTrack `json:"results"`
|
||||
}
|
||||
|
||||
type FavoriteTrack struct {
|
||||
Id int `json:"int"`
|
||||
User User `json:"user"`
|
||||
Track Track `json:"track"`
|
||||
CreationDate string `json:"creation_date"`
|
||||
}
|
||||
|
||||
type Track struct {
|
||||
Id int `json:"int"`
|
||||
Artist Artist `json:"artist"`
|
||||
Album Album `json:"album"`
|
||||
Title string `json:"title"`
|
||||
Position int `json:"position"`
|
||||
DiscNumber int `json:"disc_number"`
|
||||
RecordingMbid string `json:"mbid"`
|
||||
Tags []string `json:"tags"`
|
||||
Uploads []Upload `json:"uploads"`
|
||||
}
|
||||
|
||||
type Artist struct {
|
||||
Id int `json:"int"`
|
||||
Name string `json:"name"`
|
||||
ArtistMbid string `json:"mbid"`
|
||||
}
|
||||
|
||||
type Album struct {
|
||||
Id int `json:"int"`
|
||||
Title string `json:"title"`
|
||||
AlbumArtist Artist `json:"artist"`
|
||||
ReleaseDate string `json:"release_date"`
|
||||
TrackCount int `json:"track_count"`
|
||||
ReleaseMbid string `json:"mbid"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Id int `json:"int"`
|
||||
UserName string `json:"username"`
|
||||
}
|
||||
|
||||
type Upload struct {
|
||||
UUID string `json:"uuid"`
|
||||
Duration int `json:"duration"`
|
||||
}
|
261
internal/backends/funkwhale/testdata/favorite-tracks.json
vendored
Normal file
261
internal/backends/funkwhale/testdata/favorite-tracks.json
vendored
Normal file
|
@ -0,0 +1,261 @@
|
|||
{
|
||||
"count": 76,
|
||||
"next": "https://music.uploadedlobster.com/api/v1/favorites/tracks/?page=2&page_size=2",
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": 80,
|
||||
"user": {
|
||||
"id": 1,
|
||||
"username": "phw",
|
||||
"name": "",
|
||||
"date_joined": "2020-11-13T16:22:52.464109Z",
|
||||
"avatar": {
|
||||
"uuid": "8f87ca98-fe9e-4f7e-aa54-aa8276c25fd4",
|
||||
"size": 262868,
|
||||
"mimetype": "image/png",
|
||||
"creation_date": "2021-08-30T12:02:20.962405Z",
|
||||
"urls": {
|
||||
"source": null,
|
||||
"original": "https://music.uploadedlobster.com/media/attachments/4b/e2/c3/canned-ape.png",
|
||||
"medium_square_crop": "https://music.uploadedlobster.com/media/__sized__/attachments/4b/e2/c3/canned-ape-crop-c0-5__0-5-200x200.png",
|
||||
"large_square_crop": "https://music.uploadedlobster.com/media/__sized__/attachments/4b/e2/c3/canned-ape-crop-c0-5__0-5-600x600.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
"track": {
|
||||
"artist": {
|
||||
"id": 211,
|
||||
"fid": "https://music.uploadedlobster.com/federation/music/artists/92ab65ce-95d4-405f-a162-959e9a69ec3e",
|
||||
"mbid": "a1f2450a-c076-4a0d-ac9c-764bfc4225f7",
|
||||
"name": "Prinzhorn Dance School",
|
||||
"creation_date": "2020-11-14T08:27:27.964479Z",
|
||||
"modification_date": "2020-11-14T08:27:27.964602Z",
|
||||
"is_local": true,
|
||||
"content_category": "music",
|
||||
"description": null,
|
||||
"attachment_cover": null,
|
||||
"channel": null
|
||||
},
|
||||
"album": {
|
||||
"id": 237,
|
||||
"fid": "https://music.uploadedlobster.com/federation/music/albums/d301df18-c0b0-4608-8c68-bba86a65eabc",
|
||||
"mbid": "7cb1093c-bfa5-4ffc-b3ac-943fb5c7f39f",
|
||||
"title": "Home Economics",
|
||||
"artist": {
|
||||
"id": 211,
|
||||
"fid": "https://music.uploadedlobster.com/federation/music/artists/92ab65ce-95d4-405f-a162-959e9a69ec3e",
|
||||
"mbid": "a1f2450a-c076-4a0d-ac9c-764bfc4225f7",
|
||||
"name": "Prinzhorn Dance School",
|
||||
"creation_date": "2020-11-14T08:27:27.964479Z",
|
||||
"modification_date": "2020-11-14T08:27:27.964602Z",
|
||||
"is_local": true,
|
||||
"content_category": "music",
|
||||
"description": null,
|
||||
"attachment_cover": null,
|
||||
"channel": null
|
||||
},
|
||||
"release_date": "2015-06-08",
|
||||
"cover": {
|
||||
"uuid": "e2022441-b120-431d-912c-aa930c668cd5",
|
||||
"size": 279103,
|
||||
"mimetype": "image/jpeg",
|
||||
"creation_date": "2023-03-30T07:51:33.366860Z",
|
||||
"urls": {
|
||||
"source": null,
|
||||
"original": "https://music.uploadedlobster.com/media/attachments/98/0a/af/attachment_cover-d301df18-c0b0-4608-8c68-bba86a65eabc.jpg",
|
||||
"medium_square_crop": "https://music.uploadedlobster.com/media/__sized__/attachments/98/0a/af/attachment_cover-d301df18-c0b0-4608-8c68-bba86a65eabc-crop-c0-5__0-5-200x200-95.jpg",
|
||||
"large_square_crop": "https://music.uploadedlobster.com/media/__sized__/attachments/98/0a/af/attachment_cover-d301df18-c0b0-4608-8c68-bba86a65eabc-crop-c0-5__0-5-600x600-95.jpg"
|
||||
}
|
||||
},
|
||||
"creation_date": "2020-11-14T08:27:28.805848Z",
|
||||
"is_local": true,
|
||||
"tracks_count": 6
|
||||
},
|
||||
"uploads": [
|
||||
{
|
||||
"uuid": "6647a0d0-5119-4dd0-bf1f-dfc64a1d956c",
|
||||
"listen_url": "/api/v1/listen/b4cb522f-ce1b-49fb-81b8-0cc0d1cb5495/?upload=6647a0d0-5119-4dd0-bf1f-dfc64a1d956c",
|
||||
"size": 11147671,
|
||||
"duration": 271,
|
||||
"bitrate": 320000,
|
||||
"mimetype": "audio/mpeg",
|
||||
"extension": "mp3",
|
||||
"is_local": true
|
||||
}
|
||||
],
|
||||
"listen_url": "/api/v1/listen/b4cb522f-ce1b-49fb-81b8-0cc0d1cb5495/",
|
||||
"tags": [],
|
||||
"attributed_to": {
|
||||
"fid": "https://music.uploadedlobster.com/federation/actors/phw",
|
||||
"url": null,
|
||||
"creation_date": "2020-11-13T16:54:45.182645Z",
|
||||
"summary": null,
|
||||
"preferred_username": "phw",
|
||||
"name": "phw",
|
||||
"last_fetch_date": "2020-11-13T16:54:45.182661Z",
|
||||
"domain": "music.uploadedlobster.com",
|
||||
"type": "Person",
|
||||
"manually_approves_followers": false,
|
||||
"full_username": "phw@music.uploadedlobster.com",
|
||||
"is_local": true
|
||||
},
|
||||
"id": 2833,
|
||||
"fid": "https://music.uploadedlobster.com/federation/music/tracks/b4cb522f-ce1b-49fb-81b8-0cc0d1cb5495",
|
||||
"mbid": "b59cf4e7-caee-4019-a844-79d2c58d4dff",
|
||||
"title": "Reign",
|
||||
"creation_date": "2020-11-14T08:27:28.954529Z",
|
||||
"is_local": true,
|
||||
"position": 1,
|
||||
"disc_number": 1,
|
||||
"downloads_count": 3,
|
||||
"copyright": null,
|
||||
"license": null,
|
||||
"cover": null,
|
||||
"is_playable": true
|
||||
},
|
||||
"creation_date": "2023-11-05T20:32:32.339738Z",
|
||||
"actor": {
|
||||
"fid": "https://music.uploadedlobster.com/federation/actors/phw",
|
||||
"url": null,
|
||||
"creation_date": "2020-11-13T16:54:45.182645Z",
|
||||
"summary": null,
|
||||
"preferred_username": "phw",
|
||||
"name": "phw",
|
||||
"last_fetch_date": "2020-11-13T16:54:45.182661Z",
|
||||
"domain": "music.uploadedlobster.com",
|
||||
"type": "Person",
|
||||
"manually_approves_followers": false,
|
||||
"full_username": "phw@music.uploadedlobster.com",
|
||||
"is_local": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 79,
|
||||
"user": {
|
||||
"id": 1,
|
||||
"username": "phw",
|
||||
"name": "",
|
||||
"date_joined": "2020-11-13T16:22:52.464109Z",
|
||||
"avatar": {
|
||||
"uuid": "8f87ca98-fe9e-4f7e-aa54-aa8276c25fd4",
|
||||
"size": 262868,
|
||||
"mimetype": "image/png",
|
||||
"creation_date": "2021-08-30T12:02:20.962405Z",
|
||||
"urls": {
|
||||
"source": null,
|
||||
"original": "https://music.uploadedlobster.com/media/attachments/4b/e2/c3/canned-ape.png",
|
||||
"medium_square_crop": "https://music.uploadedlobster.com/media/__sized__/attachments/4b/e2/c3/canned-ape-crop-c0-5__0-5-200x200.png",
|
||||
"large_square_crop": "https://music.uploadedlobster.com/media/__sized__/attachments/4b/e2/c3/canned-ape-crop-c0-5__0-5-600x600.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
"track": {
|
||||
"artist": {
|
||||
"id": 3800,
|
||||
"fid": "https://music.uploadedlobster.com/federation/music/artists/2ef92f34-f4bc-4bf5-85de-a6847ea51a62",
|
||||
"mbid": "055b6082-b9cc-4688-85c4-8153c0ef2d70",
|
||||
"name": "Crippled Black Phoenix",
|
||||
"creation_date": "2023-07-19T06:39:22.078159Z",
|
||||
"modification_date": "2023-07-19T06:39:22.078238Z",
|
||||
"is_local": true,
|
||||
"content_category": "music",
|
||||
"description": null,
|
||||
"attachment_cover": null,
|
||||
"channel": null
|
||||
},
|
||||
"album": {
|
||||
"id": 2684,
|
||||
"fid": "https://music.uploadedlobster.com/federation/music/albums/138bd960-8e94-4794-adaa-51de5fba33c3",
|
||||
"mbid": "97509ec0-93cc-47ca-9033-1ac27678d799",
|
||||
"title": "Ellengæst",
|
||||
"artist": {
|
||||
"id": 3800,
|
||||
"fid": "https://music.uploadedlobster.com/federation/music/artists/2ef92f34-f4bc-4bf5-85de-a6847ea51a62",
|
||||
"mbid": "055b6082-b9cc-4688-85c4-8153c0ef2d70",
|
||||
"name": "Crippled Black Phoenix",
|
||||
"creation_date": "2023-07-19T06:39:22.078159Z",
|
||||
"modification_date": "2023-07-19T06:39:22.078238Z",
|
||||
"is_local": true,
|
||||
"content_category": "music",
|
||||
"description": null,
|
||||
"attachment_cover": null,
|
||||
"channel": null
|
||||
},
|
||||
"release_date": "2020-11-04",
|
||||
"cover": {
|
||||
"uuid": "2c191778-0c1c-467e-9bf1-9949e3d98507",
|
||||
"size": 129633,
|
||||
"mimetype": "image/jpeg",
|
||||
"creation_date": "2023-07-19T06:39:22.102600Z",
|
||||
"urls": {
|
||||
"source": null,
|
||||
"original": "https://music.uploadedlobster.com/media/attachments/e5/d6/bf/attachment_cover-138bd960-8e94-4794-adaa-51de5fba33c3.jpg",
|
||||
"medium_square_crop": "https://music.uploadedlobster.com/media/__sized__/attachments/e5/d6/bf/attachment_cover-138bd960-8e94-4794-adaa-51de5fba33c3-crop-c0-5__0-5-200x200-95.jpg",
|
||||
"large_square_crop": "https://music.uploadedlobster.com/media/__sized__/attachments/e5/d6/bf/attachment_cover-138bd960-8e94-4794-adaa-51de5fba33c3-crop-c0-5__0-5-600x600-95.jpg"
|
||||
}
|
||||
},
|
||||
"creation_date": "2023-07-19T06:39:22.094428Z",
|
||||
"is_local": true,
|
||||
"tracks_count": 8
|
||||
},
|
||||
"uploads": [
|
||||
{
|
||||
"uuid": "f28de29b-6928-4879-a9da-32dcf9dd5ee4",
|
||||
"listen_url": "/api/v1/listen/9263d5c0-dee3-4273-beef-7585e1d0041a/?upload=f28de29b-6928-4879-a9da-32dcf9dd5ee4",
|
||||
"size": 12065231,
|
||||
"duration": 491,
|
||||
"bitrate": 0,
|
||||
"mimetype": "audio/opus",
|
||||
"extension": "opus",
|
||||
"is_local": true
|
||||
}
|
||||
],
|
||||
"listen_url": "/api/v1/listen/9263d5c0-dee3-4273-beef-7585e1d0041a/",
|
||||
"tags": [],
|
||||
"attributed_to": {
|
||||
"fid": "https://music.uploadedlobster.com/federation/actors/phw",
|
||||
"url": null,
|
||||
"creation_date": "2020-11-13T16:54:45.182645Z",
|
||||
"summary": null,
|
||||
"preferred_username": "phw",
|
||||
"name": "phw",
|
||||
"last_fetch_date": "2020-11-13T16:54:45.182661Z",
|
||||
"domain": "music.uploadedlobster.com",
|
||||
"type": "Person",
|
||||
"manually_approves_followers": false,
|
||||
"full_username": "phw@music.uploadedlobster.com",
|
||||
"is_local": true
|
||||
},
|
||||
"id": 28095,
|
||||
"fid": "https://music.uploadedlobster.com/federation/music/tracks/9263d5c0-dee3-4273-beef-7585e1d0041a",
|
||||
"mbid": "14d612f0-4022-4adc-8cef-87a569e2d65c",
|
||||
"title": "Lost",
|
||||
"creation_date": "2023-07-19T06:39:22.459072Z",
|
||||
"is_local": true,
|
||||
"position": 2,
|
||||
"disc_number": 1,
|
||||
"downloads_count": 2,
|
||||
"copyright": null,
|
||||
"license": null,
|
||||
"cover": null,
|
||||
"is_playable": true
|
||||
},
|
||||
"creation_date": "2023-10-25T16:14:36.112517Z",
|
||||
"actor": {
|
||||
"fid": "https://music.uploadedlobster.com/federation/actors/phw",
|
||||
"url": null,
|
||||
"creation_date": "2020-11-13T16:54:45.182645Z",
|
||||
"summary": null,
|
||||
"preferred_username": "phw",
|
||||
"name": "phw",
|
||||
"last_fetch_date": "2020-11-13T16:54:45.182661Z",
|
||||
"domain": "music.uploadedlobster.com",
|
||||
"type": "Person",
|
||||
"manually_approves_followers": false,
|
||||
"full_username": "phw@music.uploadedlobster.com",
|
||||
"is_local": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
261
internal/backends/funkwhale/testdata/listenings.json
vendored
Normal file
261
internal/backends/funkwhale/testdata/listenings.json
vendored
Normal file
|
@ -0,0 +1,261 @@
|
|||
{
|
||||
"count": 2204,
|
||||
"next": "https://music.uploadedlobster.com/api/v1/history/listenings/?page=2&page_size=2&username=phw",
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": 2204,
|
||||
"user": {
|
||||
"id": 1,
|
||||
"username": "phw",
|
||||
"name": "",
|
||||
"date_joined": "2020-11-13T16:22:52.464109Z",
|
||||
"avatar": {
|
||||
"uuid": "8f87ca98-fe9e-4f7e-aa54-aa8276c25fd4",
|
||||
"size": 262868,
|
||||
"mimetype": "image/png",
|
||||
"creation_date": "2021-08-30T12:02:20.962405Z",
|
||||
"urls": {
|
||||
"source": null,
|
||||
"original": "https://music.uploadedlobster.com/media/attachments/4b/e2/c3/canned-ape.png",
|
||||
"medium_square_crop": "https://music.uploadedlobster.com/media/__sized__/attachments/4b/e2/c3/canned-ape-crop-c0-5__0-5-200x200.png",
|
||||
"large_square_crop": "https://music.uploadedlobster.com/media/__sized__/attachments/4b/e2/c3/canned-ape-crop-c0-5__0-5-600x600.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
"track": {
|
||||
"artist": {
|
||||
"id": 3824,
|
||||
"fid": "https://music.uploadedlobster.com/federation/music/artists/0c0fc2f3-becd-4b24-a2c4-04523cbd5934",
|
||||
"mbid": "54292079-790c-4e99-bf8d-12efa29fa3e9",
|
||||
"name": "Hazeshuttle",
|
||||
"creation_date": "2023-10-27T16:35:36.458347Z",
|
||||
"modification_date": "2023-10-27T16:35:36.458426Z",
|
||||
"is_local": true,
|
||||
"content_category": "music",
|
||||
"description": null,
|
||||
"attachment_cover": null,
|
||||
"channel": null
|
||||
},
|
||||
"album": {
|
||||
"id": 2700,
|
||||
"fid": "https://music.uploadedlobster.com/federation/music/albums/ff6bb1d6-4334-4280-95a1-0d49c64c8c7d",
|
||||
"mbid": "6d0ee27f-dc9f-4dab-8d7d-f4dcd14dc54a",
|
||||
"title": "Hazeshuttle",
|
||||
"artist": {
|
||||
"id": 3824,
|
||||
"fid": "https://music.uploadedlobster.com/federation/music/artists/0c0fc2f3-becd-4b24-a2c4-04523cbd5934",
|
||||
"mbid": "54292079-790c-4e99-bf8d-12efa29fa3e9",
|
||||
"name": "Hazeshuttle",
|
||||
"creation_date": "2023-10-27T16:35:36.458347Z",
|
||||
"modification_date": "2023-10-27T16:35:36.458426Z",
|
||||
"is_local": true,
|
||||
"content_category": "music",
|
||||
"description": null,
|
||||
"attachment_cover": null,
|
||||
"channel": null
|
||||
},
|
||||
"release_date": "2023-04-01",
|
||||
"cover": {
|
||||
"uuid": "d91a26cd-6132-4762-9f33-dba9b78ece69",
|
||||
"size": 143159,
|
||||
"mimetype": "image/jpeg",
|
||||
"creation_date": "2023-10-27T16:35:36.473913Z",
|
||||
"urls": {
|
||||
"source": null,
|
||||
"original": "https://music.uploadedlobster.com/media/attachments/5e/4d/7f/attachment_cover-ff6bb1d6-4334-4280-95a1-0d49c64c8c7d.jpg",
|
||||
"medium_square_crop": "https://music.uploadedlobster.com/media/__sized__/attachments/5e/4d/7f/attachment_cover-ff6bb1d6-4334-4280-95a1-0d49c64c8c7d-crop-c0-5__0-5-200x200-95.jpg",
|
||||
"large_square_crop": "https://music.uploadedlobster.com/media/__sized__/attachments/5e/4d/7f/attachment_cover-ff6bb1d6-4334-4280-95a1-0d49c64c8c7d-crop-c0-5__0-5-600x600-95.jpg"
|
||||
}
|
||||
},
|
||||
"creation_date": "2023-10-27T16:35:36.468407Z",
|
||||
"is_local": true,
|
||||
"tracks_count": 5
|
||||
},
|
||||
"uploads": [
|
||||
{
|
||||
"uuid": "4d3ef919-8683-42f0-bb1a-edbec258006a",
|
||||
"listen_url": "/api/v1/listen/a7fc119f-67c2-4a64-9609-d5dc3d42e3ee/?upload=4d3ef919-8683-42f0-bb1a-edbec258006a",
|
||||
"size": 13837685,
|
||||
"duration": 567,
|
||||
"bitrate": 0,
|
||||
"mimetype": "audio/opus",
|
||||
"extension": "opus",
|
||||
"is_local": true
|
||||
}
|
||||
],
|
||||
"listen_url": "/api/v1/listen/a7fc119f-67c2-4a64-9609-d5dc3d42e3ee/",
|
||||
"tags": [],
|
||||
"attributed_to": {
|
||||
"fid": "https://music.uploadedlobster.com/federation/actors/phw",
|
||||
"url": null,
|
||||
"creation_date": "2020-11-13T16:54:45.182645Z",
|
||||
"summary": null,
|
||||
"preferred_username": "phw",
|
||||
"name": "phw",
|
||||
"last_fetch_date": "2020-11-13T16:54:45.182661Z",
|
||||
"domain": "music.uploadedlobster.com",
|
||||
"type": "Person",
|
||||
"manually_approves_followers": false,
|
||||
"full_username": "phw@music.uploadedlobster.com",
|
||||
"is_local": true
|
||||
},
|
||||
"id": 28224,
|
||||
"fid": "https://music.uploadedlobster.com/federation/music/tracks/a7fc119f-67c2-4a64-9609-d5dc3d42e3ee",
|
||||
"mbid": "db8488cb-d665-4853-8e7e-970e7c2d9225",
|
||||
"title": "Way to Eden",
|
||||
"creation_date": "2023-10-27T16:35:36.661199Z",
|
||||
"is_local": true,
|
||||
"position": 2,
|
||||
"disc_number": 1,
|
||||
"downloads_count": 2,
|
||||
"copyright": null,
|
||||
"license": null,
|
||||
"cover": null,
|
||||
"is_playable": true
|
||||
},
|
||||
"creation_date": "2023-11-09T23:59:29.022005Z",
|
||||
"actor": {
|
||||
"fid": "https://music.uploadedlobster.com/federation/actors/phw",
|
||||
"url": null,
|
||||
"creation_date": "2020-11-13T16:54:45.182645Z",
|
||||
"summary": null,
|
||||
"preferred_username": "phw",
|
||||
"name": "phw",
|
||||
"last_fetch_date": "2020-11-13T16:54:45.182661Z",
|
||||
"domain": "music.uploadedlobster.com",
|
||||
"type": "Person",
|
||||
"manually_approves_followers": false,
|
||||
"full_username": "phw@music.uploadedlobster.com",
|
||||
"is_local": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2203,
|
||||
"user": {
|
||||
"id": 1,
|
||||
"username": "phw",
|
||||
"name": "",
|
||||
"date_joined": "2020-11-13T16:22:52.464109Z",
|
||||
"avatar": {
|
||||
"uuid": "8f87ca98-fe9e-4f7e-aa54-aa8276c25fd4",
|
||||
"size": 262868,
|
||||
"mimetype": "image/png",
|
||||
"creation_date": "2021-08-30T12:02:20.962405Z",
|
||||
"urls": {
|
||||
"source": null,
|
||||
"original": "https://music.uploadedlobster.com/media/attachments/4b/e2/c3/canned-ape.png",
|
||||
"medium_square_crop": "https://music.uploadedlobster.com/media/__sized__/attachments/4b/e2/c3/canned-ape-crop-c0-5__0-5-200x200.png",
|
||||
"large_square_crop": "https://music.uploadedlobster.com/media/__sized__/attachments/4b/e2/c3/canned-ape-crop-c0-5__0-5-600x600.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
"track": {
|
||||
"artist": {
|
||||
"id": 3824,
|
||||
"fid": "https://music.uploadedlobster.com/federation/music/artists/0c0fc2f3-becd-4b24-a2c4-04523cbd5934",
|
||||
"mbid": "54292079-790c-4e99-bf8d-12efa29fa3e9",
|
||||
"name": "Hazeshuttle",
|
||||
"creation_date": "2023-10-27T16:35:36.458347Z",
|
||||
"modification_date": "2023-10-27T16:35:36.458426Z",
|
||||
"is_local": true,
|
||||
"content_category": "music",
|
||||
"description": null,
|
||||
"attachment_cover": null,
|
||||
"channel": null
|
||||
},
|
||||
"album": {
|
||||
"id": 2700,
|
||||
"fid": "https://music.uploadedlobster.com/federation/music/albums/ff6bb1d6-4334-4280-95a1-0d49c64c8c7d",
|
||||
"mbid": "6d0ee27f-dc9f-4dab-8d7d-f4dcd14dc54a",
|
||||
"title": "Hazeshuttle",
|
||||
"artist": {
|
||||
"id": 3824,
|
||||
"fid": "https://music.uploadedlobster.com/federation/music/artists/0c0fc2f3-becd-4b24-a2c4-04523cbd5934",
|
||||
"mbid": "54292079-790c-4e99-bf8d-12efa29fa3e9",
|
||||
"name": "Hazeshuttle",
|
||||
"creation_date": "2023-10-27T16:35:36.458347Z",
|
||||
"modification_date": "2023-10-27T16:35:36.458426Z",
|
||||
"is_local": true,
|
||||
"content_category": "music",
|
||||
"description": null,
|
||||
"attachment_cover": null,
|
||||
"channel": null
|
||||
},
|
||||
"release_date": "2023-04-01",
|
||||
"cover": {
|
||||
"uuid": "d91a26cd-6132-4762-9f33-dba9b78ece69",
|
||||
"size": 143159,
|
||||
"mimetype": "image/jpeg",
|
||||
"creation_date": "2023-10-27T16:35:36.473913Z",
|
||||
"urls": {
|
||||
"source": null,
|
||||
"original": "https://music.uploadedlobster.com/media/attachments/5e/4d/7f/attachment_cover-ff6bb1d6-4334-4280-95a1-0d49c64c8c7d.jpg",
|
||||
"medium_square_crop": "https://music.uploadedlobster.com/media/__sized__/attachments/5e/4d/7f/attachment_cover-ff6bb1d6-4334-4280-95a1-0d49c64c8c7d-crop-c0-5__0-5-200x200-95.jpg",
|
||||
"large_square_crop": "https://music.uploadedlobster.com/media/__sized__/attachments/5e/4d/7f/attachment_cover-ff6bb1d6-4334-4280-95a1-0d49c64c8c7d-crop-c0-5__0-5-600x600-95.jpg"
|
||||
}
|
||||
},
|
||||
"creation_date": "2023-10-27T16:35:36.468407Z",
|
||||
"is_local": true,
|
||||
"tracks_count": 5
|
||||
},
|
||||
"uploads": [
|
||||
{
|
||||
"uuid": "32a6e065-cfb3-4bd5-a1a8-08416e2211f2",
|
||||
"listen_url": "/api/v1/listen/24c207d8-abeb-4541-829f-cf6c6a04a44a/?upload=32a6e065-cfb3-4bd5-a1a8-08416e2211f2",
|
||||
"size": 24121224,
|
||||
"duration": 1007,
|
||||
"bitrate": 0,
|
||||
"mimetype": "audio/opus",
|
||||
"extension": "opus",
|
||||
"is_local": true
|
||||
}
|
||||
],
|
||||
"listen_url": "/api/v1/listen/24c207d8-abeb-4541-829f-cf6c6a04a44a/",
|
||||
"tags": [],
|
||||
"attributed_to": {
|
||||
"fid": "https://music.uploadedlobster.com/federation/actors/phw",
|
||||
"url": null,
|
||||
"creation_date": "2020-11-13T16:54:45.182645Z",
|
||||
"summary": null,
|
||||
"preferred_username": "phw",
|
||||
"name": "phw",
|
||||
"last_fetch_date": "2020-11-13T16:54:45.182661Z",
|
||||
"domain": "music.uploadedlobster.com",
|
||||
"type": "Person",
|
||||
"manually_approves_followers": false,
|
||||
"full_username": "phw@music.uploadedlobster.com",
|
||||
"is_local": true
|
||||
},
|
||||
"id": 28223,
|
||||
"fid": "https://music.uploadedlobster.com/federation/music/tracks/24c207d8-abeb-4541-829f-cf6c6a04a44a",
|
||||
"mbid": "ba49cada-9873-4bdb-9506-533cb63372c8",
|
||||
"title": "Homosativa",
|
||||
"creation_date": "2023-10-27T16:35:36.559260Z",
|
||||
"is_local": true,
|
||||
"position": 1,
|
||||
"disc_number": 1,
|
||||
"downloads_count": 1,
|
||||
"copyright": null,
|
||||
"license": null,
|
||||
"cover": null,
|
||||
"is_playable": true
|
||||
},
|
||||
"creation_date": "2023-11-09T23:42:42.345349Z",
|
||||
"actor": {
|
||||
"fid": "https://music.uploadedlobster.com/federation/actors/phw",
|
||||
"url": null,
|
||||
"creation_date": "2020-11-13T16:54:45.182645Z",
|
||||
"summary": null,
|
||||
"preferred_username": "phw",
|
||||
"name": "phw",
|
||||
"last_fetch_date": "2020-11-13T16:54:45.182661Z",
|
||||
"domain": "music.uploadedlobster.com",
|
||||
"type": "Person",
|
||||
"manually_approves_followers": false,
|
||||
"full_username": "phw@music.uploadedlobster.com",
|
||||
"is_local": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue