def library_library(request, uuid, redirect_to_ap): queryset = models.Library.objects.filter(uuid=uuid) try: obj = queryset.get() except models.Library.DoesNotExist: return [] if redirect_to_ap: raise middleware.ApiRedirect(obj.fid) library_url = utils.join_url( settings.FUNKWHALE_URL, utils.spa_reverse("library_library", kwargs={"uuid": obj.uuid}), ) metas = [ {"tag": "meta", "property": "og:url", "content": library_url}, {"tag": "meta", "property": "og:type", "content": "website"}, {"tag": "meta", "property": "og:title", "content": obj.name}, {"tag": "meta", "property": "og:description", "content": obj.description}, ] if preferences.get("federation__enabled"): metas.append( { "tag": "link", "rel": "alternate", "type": "application/activity+json", "href": obj.fid, } ) return metas
def actor_detail_username(request, username, redirect_to_ap): validator = federation_utils.get_actor_data_from_username try: username_data = validator(username) except serializers.ValidationError: return [] queryset = (models.Actor.objects.filter( preferred_username__iexact=username_data["username"]).local(). select_related("attachment_icon")) try: obj = queryset.get() except models.Actor.DoesNotExist: return [] if redirect_to_ap: raise middleware.ApiRedirect(obj.fid) obj_url = utils.join_url( settings.FUNKWHALE_URL, utils.spa_reverse("actor_detail", kwargs={"username": obj.preferred_username}), ) metas = [ { "tag": "meta", "property": "og:url", "content": obj_url }, { "tag": "meta", "property": "og:title", "content": obj.display_name }, { "tag": "meta", "property": "og:type", "content": "profile" }, ] if obj.attachment_icon: metas.append({ "tag": "meta", "property": "og:image", "content": obj.attachment_icon.download_url_medium_square_crop, }) if preferences.get("federation__enabled"): metas.append({ "tag": "link", "rel": "alternate", "type": "application/activity+json", "href": obj.fid, }) return metas
def test_music_library_retrieve_redirects_to_html_if_header_set( factories, api_client, settings): library = factories["music.Library"](actor__local=True) url = reverse("federation:music:libraries-detail", kwargs={"uuid": library.uuid}) response = api_client.get(url, HTTP_ACCEPT="text/html") expected_url = utils.join_url( settings.FUNKWHALE_URL, utils.spa_reverse("library_library", kwargs={"uuid": library.uuid}), ) assert response.status_code == 302 assert response["Location"] == expected_url
def library_playlist(request, pk, redirect_to_ap): queryset = playlists_models.Playlist.objects.filter(pk=pk, privacy_level="everyone") try: obj = queryset.get() except playlists_models.Playlist.DoesNotExist: return [] obj_url = utils.join_url( settings.FUNKWHALE_URL, utils.spa_reverse("library_playlist", kwargs={"pk": obj.pk}), ) # we use the first playlist track's album's cover as image playlist_tracks = obj.playlist_tracks.exclude(track__album__attachment_cover=None) playlist_tracks = playlist_tracks.select_related("track__album").order_by("index") first_playlist_track = playlist_tracks.first() metas = [ {"tag": "meta", "property": "og:url", "content": obj_url}, {"tag": "meta", "property": "og:title", "content": obj.name}, {"tag": "meta", "property": "og:type", "content": "music.playlist"}, ] if first_playlist_track: metas.append( { "tag": "meta", "property": "og:image", "content": first_playlist_track.track.album.attachment_cover.download_url_medium_square_crop, } ) if ( models.Upload.objects.filter( track__pk__in=[obj.playlist_tracks.values("track")] ) .playable_by(None) .exists() ): metas.append( { "tag": "link", "rel": "alternate", "type": "application/json+oembed", "href": ( utils.join_url(settings.FUNKWHALE_URL, reverse("api:v1:oembed")) + "?format=json&url={}".format(urllib.parse.quote_plus(obj_url)) ), } ) # twitter player is also supported in various software metas += get_twitter_card_metas(type="playlist", id=obj.pk) return metas
def test_upload_retrieve_redirects_to_html_if_header_set( factories, api_client, settings): upload = factories["music.Upload"](library__local=True, playable=True) url = reverse( "federation:music:uploads-detail", kwargs={"uuid": upload.uuid}, ) response = api_client.get(url, HTTP_ACCEPT="text/html") expected_url = utils.join_url( settings.FUNKWHALE_URL, utils.spa_reverse("library_track", kwargs={"pk": upload.track.pk}), ) assert response.status_code == 302 assert response["Location"] == expected_url
def test_actor_retrieve_redirects_to_html_if_header_set( factories, api_client, settings): actor = factories["federation.Actor"](local=True) url = reverse( "federation:actors-detail", kwargs={"preferred_username": actor.preferred_username}, ) response = api_client.get(url, HTTP_ACCEPT="text/html") expected_url = utils.join_url( settings.FUNKWHALE_URL, utils.spa_reverse("actor_detail", kwargs={"username": actor.preferred_username}), ) assert response.status_code == 302 assert response["Location"] == expected_url
def test_oembed_channel(factories, no_api_auth, api_client, settings): settings.FUNKWHALE_URL = "http://test" settings.FUNKWHALE_EMBED_URL = "http://embed" channel = factories["audio.Channel"](artist__with_cover=True) artist = channel.artist url = reverse("api:v1:oembed") obj_url = "https://test.com/channels/{}".format(channel.uuid) iframe_src = "http://embed?type=channel&id={}".format(channel.uuid) expected = { "version": "1.0", "type": "rich", "provider_name": settings.APP_NAME, "provider_url": settings.FUNKWHALE_URL, "height": 400, "width": 600, "title": artist.name, "description": artist.name, "thumbnail_url": federation_utils.full_url( artist.attachment_cover.file.crop["200x200"].url), "thumbnail_height": 200, "thumbnail_width": 200, "html": '<iframe width="600" height="400" scrolling="no" frameborder="no" src="{}"></iframe>' .format(iframe_src), "author_name": artist.name, "author_url": federation_utils.full_url( utils.spa_reverse("channel_detail", kwargs={"uuid": channel.uuid})), } response = api_client.get(url, {"url": obj_url, "format": "json"}) assert response.data == expected
def test_spa_views_raise_api_redirect_when_accept_json_set( factory_name, factory_kwargs, route_name, route_arg_name, route_arg, factories, fake_request, ): obj = factories[factory_name](**factory_kwargs) url = utils.spa_reverse( route_name, kwargs={route_arg_name: utils.recursive_getattr(obj, route_arg)}) request = fake_request.get(url, HTTP_ACCEPT="application/activity+json") with pytest.raises(middleware.ApiRedirect) as excinfo: middleware.get_request_head_tags(request) assert excinfo.value.url == obj.fid
def library_track(request, pk, redirect_to_ap): queryset = models.Track.objects.filter(pk=pk).select_related("album", "artist") try: obj = queryset.get() except models.Track.DoesNotExist: return [] playable_uploads = obj.uploads.playable_by(None).order_by("id") upload = playable_uploads.first() if redirect_to_ap: redirect_url = upload.fid if upload else obj.fid raise middleware.ApiRedirect(redirect_url) track_url = utils.join_url( settings.FUNKWHALE_URL, utils.spa_reverse("library_track", kwargs={"pk": obj.pk}), ) metas = [ {"tag": "meta", "property": "og:url", "content": track_url}, {"tag": "meta", "property": "og:title", "content": obj.title}, {"tag": "meta", "property": "og:type", "content": "music.song"}, {"tag": "meta", "property": "music:album:disc", "content": obj.disc_number}, {"tag": "meta", "property": "music:album:track", "content": obj.position}, { "tag": "meta", "property": "music:musician", "content": utils.join_url( settings.FUNKWHALE_URL, utils.spa_reverse("library_artist", kwargs={"pk": obj.artist.pk}), ), }, ] if obj.album: metas.append( { "tag": "meta", "property": "music:album", "content": utils.join_url( settings.FUNKWHALE_URL, utils.spa_reverse("library_album", kwargs={"pk": obj.album.pk}), ), }, ) if obj.attachment_cover: metas.append( { "tag": "meta", "property": "og:image", "content": obj.attachment_cover.download_url_medium_square_crop, } ) elif obj.album and obj.album.attachment_cover: metas.append( { "tag": "meta", "property": "og:image", "content": obj.album.attachment_cover.download_url_medium_square_crop, } ) if upload: metas.append( { "tag": "meta", "property": "og:audio", "content": utils.join_url(settings.FUNKWHALE_URL, obj.listen_url), } ) if preferences.get("federation__enabled"): metas.append( { "tag": "link", "rel": "alternate", "type": "application/activity+json", "href": upload.fid, } ) metas.append( { "tag": "link", "rel": "alternate", "type": "application/json+oembed", "href": ( utils.join_url(settings.FUNKWHALE_URL, reverse("api:v1:oembed")) + "?format=json&url={}".format(urllib.parse.quote_plus(track_url)) ), } ) # twitter player is also supported in various software metas += get_twitter_card_metas(type="track", id=obj.pk) return metas
def library_artist(request, pk, redirect_to_ap): queryset = models.Artist.objects.filter(pk=pk) try: obj = queryset.get() except models.Artist.DoesNotExist: return [] if redirect_to_ap: raise middleware.ApiRedirect(obj.fid) artist_url = utils.join_url( settings.FUNKWHALE_URL, utils.spa_reverse("library_artist", kwargs={"pk": obj.pk}), ) # we use latest album's cover as artist image latest_album = ( obj.albums.exclude(attachment_cover=None).order_by("release_date").last() ) metas = [ {"tag": "meta", "property": "og:url", "content": artist_url}, {"tag": "meta", "property": "og:title", "content": obj.name}, {"tag": "meta", "property": "og:type", "content": "profile"}, ] if latest_album and latest_album.attachment_cover: metas.append( { "tag": "meta", "property": "og:image", "content": latest_album.attachment_cover.download_url_medium_square_crop, } ) if preferences.get("federation__enabled"): metas.append( { "tag": "link", "rel": "alternate", "type": "application/activity+json", "href": obj.fid, } ) if ( models.Upload.objects.filter(Q(track__artist=obj) | Q(track__album__artist=obj)) .playable_by(None) .exists() ): metas.append( { "tag": "link", "rel": "alternate", "type": "application/json+oembed", "href": ( utils.join_url(settings.FUNKWHALE_URL, reverse("api:v1:oembed")) + "?format=json&url={}".format(urllib.parse.quote_plus(artist_url)) ), } ) # twitter player is also supported in various software metas += get_twitter_card_metas(type="artist", id=obj.pk) return metas
def library_album(request, pk, redirect_to_ap): queryset = models.Album.objects.filter(pk=pk).select_related("artist") try: obj = queryset.get() except models.Album.DoesNotExist: return [] if redirect_to_ap: raise middleware.ApiRedirect(obj.fid) album_url = utils.join_url( settings.FUNKWHALE_URL, utils.spa_reverse("library_album", kwargs={"pk": obj.pk}), ) metas = [ {"tag": "meta", "property": "og:url", "content": album_url}, {"tag": "meta", "property": "og:title", "content": obj.title}, {"tag": "meta", "property": "og:type", "content": "music.album"}, { "tag": "meta", "property": "music:musician", "content": utils.join_url( settings.FUNKWHALE_URL, utils.spa_reverse("library_artist", kwargs={"pk": obj.artist.pk}), ), }, ] if obj.release_date: metas.append( { "tag": "meta", "property": "music:release_date", "content": str(obj.release_date), } ) if obj.attachment_cover: metas.append( { "tag": "meta", "property": "og:image", "content": obj.attachment_cover.download_url_medium_square_crop, } ) if preferences.get("federation__enabled"): metas.append( { "tag": "link", "rel": "alternate", "type": "application/activity+json", "href": obj.fid, } ) if models.Upload.objects.filter(track__album=obj).playable_by(None).exists(): metas.append( { "tag": "link", "rel": "alternate", "type": "application/json+oembed", "href": ( utils.join_url(settings.FUNKWHALE_URL, reverse("api:v1:oembed")) + "?format=json&url={}".format(urllib.parse.quote_plus(album_url)) ), } ) # twitter player is also supported in various software metas += get_twitter_card_metas(type="album", id=obj.pk) return metas
def validate(self, validated_data): try: match = common_utils.spa_resolve( urllib.parse.urlparse(validated_data["url"]).path ) except urls.exceptions.Resolver404: raise serializers.ValidationError( "Invalid URL {}".format(validated_data["url"]) ) data = { "version": "1.0", "type": "rich", "provider_name": settings.APP_NAME, "provider_url": settings.FUNKWHALE_URL, "height": validated_data.get("maxheight") or 400, "width": validated_data.get("maxwidth") or 600, } embed_id = None embed_type = None if match.url_name == "library_track": qs = models.Track.objects.select_related("artist", "album__artist").filter( pk=int(match.kwargs["pk"]) ) try: track = qs.get() except models.Track.DoesNotExist: raise serializers.ValidationError( "No track matching id {}".format(match.kwargs["pk"]) ) embed_type = "track" embed_id = track.pk data["title"] = "{} by {}".format(track.title, track.artist.name) if track.attachment_cover: data[ "thumbnail_url" ] = track.attachment_cover.download_url_medium_square_crop data["thumbnail_width"] = 200 data["thumbnail_height"] = 200 elif track.album and track.album.attachment_cover: data[ "thumbnail_url" ] = track.album.attachment_cover.download_url_medium_square_crop data["thumbnail_width"] = 200 data["thumbnail_height"] = 200 data["description"] = track.full_name data["author_name"] = track.artist.name data["height"] = 150 data["author_url"] = federation_utils.full_url( common_utils.spa_reverse( "library_artist", kwargs={"pk": track.artist.pk} ) ) elif match.url_name == "library_album": qs = models.Album.objects.select_related("artist").filter( pk=int(match.kwargs["pk"]) ) try: album = qs.get() except models.Album.DoesNotExist: raise serializers.ValidationError( "No album matching id {}".format(match.kwargs["pk"]) ) embed_type = "album" embed_id = album.pk if album.attachment_cover: data[ "thumbnail_url" ] = album.attachment_cover.download_url_medium_square_crop data["thumbnail_width"] = 200 data["thumbnail_height"] = 200 data["title"] = "{} by {}".format(album.title, album.artist.name) data["description"] = "{} by {}".format(album.title, album.artist.name) data["author_name"] = album.artist.name data["height"] = 400 data["author_url"] = federation_utils.full_url( common_utils.spa_reverse( "library_artist", kwargs={"pk": album.artist.pk} ) ) elif match.url_name == "library_artist": qs = models.Artist.objects.filter(pk=int(match.kwargs["pk"])) try: artist = qs.get() except models.Artist.DoesNotExist: raise serializers.ValidationError( "No artist matching id {}".format(match.kwargs["pk"]) ) embed_type = "artist" embed_id = artist.pk album = artist.albums.exclude(attachment_cover=None).order_by("-id").first() if album and album.attachment_cover: data[ "thumbnail_url" ] = album.attachment_cover.download_url_medium_square_crop data["thumbnail_width"] = 200 data["thumbnail_height"] = 200 data["title"] = artist.name data["description"] = artist.name data["author_name"] = artist.name data["height"] = 400 data["author_url"] = federation_utils.full_url( common_utils.spa_reverse("library_artist", kwargs={"pk": artist.pk}) ) elif match.url_name == "channel_detail": from funkwhale_api.audio.models import Channel kwargs = {} if "uuid" in match.kwargs: kwargs["uuid"] = match.kwargs["uuid"] else: username_data = federation_utils.get_actor_data_from_username( match.kwargs["username"] ) kwargs["actor__domain"] = username_data["domain"] kwargs["actor__preferred_username__iexact"] = username_data["username"] qs = Channel.objects.filter(**kwargs).select_related( "artist__attachment_cover" ) try: channel = qs.get() except models.Artist.DoesNotExist: raise serializers.ValidationError( "No channel matching id {}".format(match.kwargs["uuid"]) ) embed_type = "channel" embed_id = channel.uuid if channel.artist.attachment_cover: data[ "thumbnail_url" ] = channel.artist.attachment_cover.download_url_medium_square_crop data["thumbnail_width"] = 200 data["thumbnail_height"] = 200 data["title"] = channel.artist.name data["description"] = channel.artist.name data["author_name"] = channel.artist.name data["height"] = 400 data["author_url"] = federation_utils.full_url( common_utils.spa_reverse( "channel_detail", kwargs={"uuid": channel.uuid} ) ) elif match.url_name == "library_playlist": qs = playlists_models.Playlist.objects.filter( pk=int(match.kwargs["pk"]), privacy_level="everyone" ) try: obj = qs.get() except playlists_models.Playlist.DoesNotExist: raise serializers.ValidationError( "No artist matching id {}".format(match.kwargs["pk"]) ) embed_type = "playlist" embed_id = obj.pk playlist_tracks = obj.playlist_tracks.exclude( track__album__attachment_cover=None ) playlist_tracks = playlist_tracks.select_related( "track__album__attachment_cover" ).order_by("index") first_playlist_track = playlist_tracks.first() if first_playlist_track: data[ "thumbnail_url" ] = ( first_playlist_track.track.album.attachment_cover.download_url_medium_square_crop ) data["thumbnail_width"] = 200 data["thumbnail_height"] = 200 data["title"] = obj.name data["description"] = obj.name data["author_name"] = obj.name data["height"] = 400 data["author_url"] = federation_utils.full_url( common_utils.spa_reverse("library_playlist", kwargs={"pk": obj.pk}) ) else: raise serializers.ValidationError( "Unsupported url: {}".format(validated_data["url"]) ) data[ "html" ] = '<iframe width="{}" height="{}" scrolling="no" frameborder="no" src="{}"></iframe>'.format( data["width"], data["height"], get_embed_url(embed_type, embed_id) ) return data
def channel_detail(query, redirect_to_ap): queryset = models.Channel.objects.filter(query).select_related( "artist__attachment_cover", "actor", "library") try: obj = queryset.get() except models.Channel.DoesNotExist: return [] if redirect_to_ap: raise middleware.ApiRedirect(obj.actor.fid) obj_url = utils.join_url( settings.FUNKWHALE_URL, utils.spa_reverse("channel_detail", kwargs={"username": obj.actor.full_username}), ) metas = [ { "tag": "meta", "property": "og:url", "content": obj_url }, { "tag": "meta", "property": "og:title", "content": obj.artist.name }, { "tag": "meta", "property": "og:type", "content": "profile" }, ] if obj.artist.attachment_cover: metas.append({ "tag": "meta", "property": "og:image", "content": obj.artist.attachment_cover.download_url_medium_square_crop, }) if preferences.get("federation__enabled"): metas.append({ "tag": "link", "rel": "alternate", "type": "application/activity+json", "href": obj.actor.fid, }) metas.append( { "tag": "link", "rel": "alternate", "type": "application/rss+xml", "href": obj.get_rss_url(), "title": "{} - RSS Podcast Feed".format(obj.artist.name), }, ) if obj.library.uploads.all().playable_by(None).exists(): metas.append({ "tag": "link", "rel": "alternate", "type": "application/json+oembed", "href": (utils.join_url(settings.FUNKWHALE_URL, reverse("api:v1:oembed")) + "?format=json&url={}".format(urllib.parse.quote_plus(obj_url))), }) # twitter player is also supported in various software metas += spa_views.get_twitter_card_metas(type="channel", id=obj.uuid) return metas
def test_library_track(spa_html, no_api_auth, client, factories, settings): upload = factories["music.Upload"]( playable=True, track__disc_number=1, track__attachment_cover=None, track__album__with_cover=True, ) track = upload.track url = "/library/tracks/{}".format(track.pk) response = client.get(url) expected_metas = [ { "tag": "meta", "property": "og:url", "content": utils.join_url(settings.FUNKWHALE_URL, url), }, { "tag": "meta", "property": "og:title", "content": track.title }, { "tag": "meta", "property": "og:type", "content": "music.song" }, { "tag": "meta", "property": "music:album:disc", "content": str(track.disc_number), }, { "tag": "meta", "property": "music:album:track", "content": str(track.position), }, { "tag": "meta", "property": "music:musician", "content": utils.join_url( settings.FUNKWHALE_URL, utils.spa_reverse("library_artist", kwargs={"pk": track.artist.pk}), ), }, { "tag": "meta", "property": "music:album", "content": utils.join_url( settings.FUNKWHALE_URL, utils.spa_reverse("library_album", kwargs={"pk": track.album.pk}), ), }, { "tag": "meta", "property": "og:image", "content": track.album.attachment_cover.download_url_medium_square_crop, }, { "tag": "meta", "property": "og:audio", "content": utils.join_url(settings.FUNKWHALE_URL, track.listen_url), }, { "tag": "link", "rel": "alternate", "type": "application/activity+json", "href": upload.fid, }, { "tag": "link", "rel": "alternate", "type": "application/json+oembed", "href": (utils.join_url(settings.FUNKWHALE_URL, reverse("api:v1:oembed")) + "?format=json&url={}".format( urllib.parse.quote_plus( utils.join_url(settings.FUNKWHALE_URL, url)))), }, { "tag": "meta", "property": "twitter:card", "content": "player" }, { "tag": "meta", "property": "twitter:player", "content": serializers.get_embed_url("track", id=track.id), }, { "tag": "meta", "property": "twitter:player:width", "content": "600" }, { "tag": "meta", "property": "twitter:player:height", "content": "400" }, ] metas = utils.parse_meta(response.content.decode()) # we only test our custom metas, not the default ones assert metas[:len(expected_metas)] == expected_metas