diff --git a/funkwhale_cli/cli/__init__.py b/funkwhale_cli/cli/__init__.py index 2ae0ba71eac37e0f60f0a24546a516c532ce0be8..ce007b70aa04b714fe388b7fd1c6c7e02f4fd130 100644 --- a/funkwhale_cli/cli/__init__.py +++ b/funkwhale_cli/cli/__init__.py @@ -1,6 +1,7 @@ from . import auth from . import albums from . import artists +from . import channels from . import favorites from . import libraries from . import playlists @@ -14,6 +15,7 @@ __all__ = [ "auth", "albums", "artists", + "channels", "favorites", "libraries", "playlists", diff --git a/funkwhale_cli/cli/base.py b/funkwhale_cli/cli/base.py index e4f86767a51babdc99bdd7000c7a7eefffadd5a0..d2ad3653bad150f10564807850296bd4275f40db 100644 --- a/funkwhale_cli/cli/base.py +++ b/funkwhale_cli/cli/base.py @@ -135,6 +135,14 @@ def async_command(f): return functools.update_wrapper(wrapper, f) +async def check_status(response): + text = await response.text() + try: + response.raise_for_status() + except aiohttp.client_exceptions.ClientError as e: + raise click.ClickException(str(e) + ": {}".format(text)) + + SERVER_DECORATOR = click.option( "-H", "--url", @@ -240,6 +248,7 @@ def get_ls_command( pagination=True, filter=True, ordering=True, + scope=False, with_id=False, owned_conf=None, name="ls", @@ -271,6 +280,8 @@ def get_ls_command( ordering_decorator = ( click.option("--ordering", "-o", default=None) if ordering else noop_decorator ) + + scope_decorator = click.option("--scope", default=None) if scope else noop_decorator filter_decorator = ( click.option("--filter", "-f", multiple=True) if filter else noop_decorator ) @@ -291,6 +302,7 @@ def get_ls_command( @page_decorator @page_size_decorator @ordering_decorator + @scope_decorator @filter_decorator @limit_decorator @owned_decorator @@ -310,6 +322,7 @@ def get_ls_command( page = kwargs.get("page") page_size = kwargs.get("page_size") ordering = kwargs.get("ordering") + scope = kwargs.get("scope") filter = kwargs.get("filter") query = kwargs.get("query") owned = kwargs.get("owned") @@ -335,6 +348,8 @@ def get_ls_command( params["page_size"] = page_size if ordering: params["ordering"] = ordering + if scope: + params["scope"] = scope if query: params["q"] = " ".join(query) if filter: diff --git a/funkwhale_cli/cli/channels.py b/funkwhale_cli/cli/channels.py new file mode 100644 index 0000000000000000000000000000000000000000..eefb6147187e5dfdfef140d3d78d493f5d50c043 --- /dev/null +++ b/funkwhale_cli/cli/channels.py @@ -0,0 +1,118 @@ +import click +import json + +from . import base +from .. import output + + +@base.cli.group() +@click.pass_context +def channels(ctx): + """ + Manage channels + """ + + +channels_ls = base.get_ls_command( + channels, + "api/v1/channels/", + scope=True, + output_conf={ + "labels": ["UUID", "Name", "Category", "Username", "Tags"], + "type": "CHANNEL", + }, +) +channels_rm = base.get_delete_command(channels, "api/v1/channels/{}/") + + +@channels.command("create") +@click.option("--name", prompt=True) +@click.option("--username", prompt=True) +@click.option("--cover") +@click.option("--description") +@click.option("--tags", default="") +@click.option("--itunes-category", default=None) +@click.option("--language", default=None) +@click.option( + "--content-category", + type=click.Choice(["music", "podcast", "other"]), + default="podcast", + prompt=True, +) +@click.option("--raw", is_flag=True) +@click.pass_context +@base.async_command +async def channels_create( + ctx, + raw, + name, + username, + cover, + description, + tags, + language, + itunes_category, + content_category, +): + data = { + "name": name, + "username": username, + "content_category": content_category, + } + if description: + data["description"] = { + "text": description, + "content_type": "text/markdown", + } + else: + data["description"] = None + + if cover: + data["cover"] = cover + + missing_fields = [] + if not itunes_category: + missing_fields.append(("--itunes-category", "itunes_category")) + if not language: + missing_fields.append(("--language", "language")) + + if content_category == "podcast" and missing_fields: + click.secho( + "You must provide adequate values for these fields: {}. Allowed values are listed below".format( + ", ".join([f for f, _ in missing_fields]) + ), + fg="red", + ) + + async with ctx.obj["remote"]: + result = await ctx.obj["remote"].request( + "get", "api/v1/channels/metadata-choices" + ) + choices = await result.json() + + for flag, field in missing_fields: + click.echo( + "{}: {}".format(flag, ", ".join([c["value"] for c in choices[field]])) + ) + + raise click.ClickException("Missing params") + data["tags"] = [t.strip() for t in tags.replace(" ", ",").split(",") if t] + if content_category == "podcast": + data["metadata"] = { + "itunes_category": itunes_category, + "language": language, + } + + async with ctx.obj["remote"]: + result = await ctx.obj["remote"].request("post", "api/v1/channels/", json=data) + await base.check_status(result) + payload = await result.json() + if raw: + click.echo(json.dumps(payload, sort_keys=True, indent=4)) + else: + click.echo("Channel created:") + click.echo( + output.table( + [payload], ["UUID", "Name", "Category", "Username"], type="CHANNEL" + ) + ) diff --git a/funkwhale_cli/output.py b/funkwhale_cli/output.py index 0616153f77ca5f18c3185bd84abdbf7d5e0d982d..958e779e53ca60d3b910015f6995656ed80a8eef 100644 --- a/funkwhale_cli/output.py +++ b/funkwhale_cli/output.py @@ -1,6 +1,10 @@ import tabulate +def comma_separated(it): + return ", ".join(it) + + FIELDS = { "ARTIST": { "ID": {"field": "id", "truncate": 0}, @@ -19,6 +23,16 @@ FIELDS = { "Tracks": {"field": "tracks", "handler": lambda v: len(v), "truncate": 0}, "Artist": {"field": "artist.name"}, }, + "CHANNEL": { + "Category": {"field": "artist.content_category", "truncate": 0}, + "Username": {"field": "actor.full_username", "truncate": 0}, + "Name": {"field": "artist.name"}, + "Descsription": {"field": "artist.description.text"}, + "RSS URL": {"field": "rss_url"}, + "Artist": {"field": "artist.id"}, + "Metadata": {"field": "metadata"}, + "Tags": {"field": "artist.tags", "handler": ", ".join,}, + }, "TRACK": { "ID": {"field": "id", "truncate": 0}, "Title": {"field": "title"}, @@ -75,6 +89,7 @@ FIELDS = { "Modified": {"field": "modification_date"}, "UUID": {"field": "uuid", "truncate": 0}, "ID": {"field": "id", "truncate": 0}, + "Tags": {"field": "tags", "handler": ", ".join,}, }, } diff --git a/tests/test_cli.py b/tests/test_cli.py index 5d66cc7c2cbde3c339f9271c62f0723eb4a3ec7f..4b20b557e09affb1a407f186eb1b05c634be0125 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -467,3 +467,105 @@ def test_playlists_tracks(cli_ctx, session, responses, get_requests): requests = get_requests("get", url) assert len(requests) == 1 + + +def test_channel_create_music(cli_ctx, session, responses, get_requests): + command = cli.channels.channels_create + url = "https://test.funkwhale/api/v1/channels/" + responses.post(url) + + command.callback( + name="test", + cover="uuid", + username="hello", + content_category="music", + description="description text", + tags='punk,rock, ska', + language=None, + itunes_category=None, + raw=False, + _async_reraise=True + ) + + requests = get_requests("post", url) + assert len(requests) == 1 + assert requests[0].kwargs["json"] == { + "name": "test", + "cover": "uuid", + "username": "hello", + "content_category": "music", + "description": {"text": "description text", "content_type": "text/markdown"}, + "tags": ["punk", "rock", "ska"], + } + + +def test_channel_create_podcast(cli_ctx, session, responses, get_requests): + command = cli.channels.channels_create + url = "https://test.funkwhale/api/v1/channels/" + responses.post(url) + + command.callback( + name="test", + cover="uuid", + username="hello", + content_category="podcast", + description="description text", + tags='punk,rock, ska', + language="en", + itunes_category="Leisure", + raw=False, + _async_reraise=True + ) + + requests = get_requests("post", url) + assert len(requests) == 1 + assert requests[0].kwargs["json"] == { + "name": "test", + "cover": "uuid", + "username": "hello", + "content_category": "podcast", + "description": {"text": "description text", "content_type": "text/markdown"}, + "tags": ["punk", "rock", "ska"], + "metadata": { + "itunes_category": "Leisure", + "language": "en", + } + } + + +def test_channels_ls(cli_ctx, session, responses, get_requests): + command = cli.channels.channels_ls + url = "https://test.funkwhale/api/v1/channels/?ordering=-creation_date&page=1&page_size=5&q=hello&scope=me" + responses.get( + url, payload={"results": [], "next": None, "previous": None, "count": 0} + ) + + command.callback( + raw=False, + page=1, + page_size=5, + ordering="-creation_date", + filter="subscribed=true", + scope="me", + query=["hello"], + column=None, + format=None, + no_headers=False, + ids=False, + limit=1, + ) + + requests = get_requests("get", url) + assert len(requests) == 1 + + +def test_channels_rm(cli_ctx, session, responses, get_requests): + command = cli.channels.channels_rm + url = "https://test.funkwhale/api/v1/channels/" + responses.delete(url + "uuid1/") + responses.delete(url + "uuid2/") + + command.callback(id=["uuid1", "uuid2"], raw=False, no_input=True, _async_reraise=True) + + assert len(get_requests("delete", url + "uuid1/")) == 1 + assert len(get_requests("delete", url + "uuid2/")) == 1