diff --git a/funkwhale_cli/cli.py b/funkwhale_cli/cli.py
index 2feefef7fcbabf73be95791165bb569bbd0843d3..39eb281b976dac361955c5074ca3b31d38c90e44 100644
--- a/funkwhale_cli/cli.py
+++ b/funkwhale_cli/cli.py
@@ -92,6 +92,10 @@ def ignore_aiohttp_ssl_eror(loop):
loop.set_exception_handler(ignore_ssl_error)
+def noop_decorator(f):
+ return f
+
+
def URL(v):
if v is NOOP:
raise click.ClickException(
@@ -385,25 +389,57 @@ def get_pagination_data(payload):
return data
-def get_ls_command(group, endpoint, output_conf):
+def get_ls_command(
+ group,
+ endpoint,
+ output_conf,
+ pagination=True,
+ filter=True,
+ ordering=True,
+ with_id=False,
+ name="ls",
+):
available_fields = sorted(
set(output_conf["labels"]) | set(output.FIELDS["*"].keys())
)
+ id_decorator = click.argument("id") if with_id else noop_decorator
+ page_decorator = (
+ click.option("--page", "-p", type=click.INT, default=1)
+ if pagination
+ else noop_decorator
+ )
+ page_size_decorator = (
+ click.option("--page-size", "-s", type=click.INT, default=None)
+ if pagination
+ else noop_decorator
+ )
+ limit_decorator = (
+ click.option("--limit", "-l", type=click.INT, default=1)
+ if pagination
+ else noop_decorator
+ )
+ ordering_decorator = (
+ click.option("--ordering", "-o", default=None) if ordering else noop_decorator
+ )
+ filter_decorator = (
+ click.option("--filter", "-f", multiple=True) if filter else noop_decorator
+ )
- @group.command("ls")
+ @group.command(name)
+ @id_decorator
@click.argument("query", nargs=-1)
@RAW_DECORATOR
@click.option(
"--format", "-t", type=click.Choice(output.TABLE_FORMATS), default="simple"
)
@click.option("--no-headers", "-h", is_flag=True, default=False)
- @click.option("--page", "-p", type=click.INT, default=1)
- @click.option("--page-size", "-s", type=click.INT, default=None)
- @click.option("--ordering", "-o", default=None)
- @click.option("--filter", "-f", multiple=True)
@click.option("--ids", "-i", is_flag=True)
- @click.option("--limit", "-l", type=click.INT, default=1)
+ @page_decorator
+ @page_size_decorator
+ @ordering_decorator
+ @filter_decorator
+ @limit_decorator
@click.option(
"--column",
"-c",
@@ -414,33 +450,32 @@ def get_ls_command(group, endpoint, output_conf):
)
@click.pass_context
@async_command
- async def ls(
- ctx,
- raw,
- page,
- page_size,
- ordering,
- filter,
- query,
- column,
- format,
- no_headers,
- ids,
- limit,
- ):
+ async def ls(ctx, raw, column, format, no_headers, ids, **kwargs):
+ id = kwargs.get("id")
+ limit = kwargs.get("limit")
+ page = kwargs.get("page")
+ page_size = kwargs.get("page_size")
+ ordering = kwargs.get("ordering")
+ filter = kwargs.get("filter")
+ query = kwargs.get("query")
if ids:
no_headers = True
column = [output_conf.get("id_field", "UUID")]
format = "plain"
+ base_url = endpoint
+ if with_id:
+ base_url = base_url.format(id)
next_page_url = None
page_count = 0
while True:
if limit and page_count >= limit:
break
async with ctx.obj["remote"]:
- if page_count == 0:
- url = endpoint
- params = {"page": page}
+ if not pagination or page_count == 0:
+ url = base_url
+ params = {}
+ if page:
+ params["page"] = page
if page_size:
params["page_size"] = page_size
if ordering:
@@ -460,7 +495,7 @@ def get_ls_command(group, endpoint, output_conf):
result = await ctx.obj["remote"].request("get", url, params=params)
result.raise_for_status()
payload = await result.json()
- next_page_url = payload["next"]
+ next_page_url = payload.get("next")
page_count += 1
if raw:
click.echo(json.dumps(payload, sort_keys=True, indent=4))
@@ -474,6 +509,8 @@ def get_ls_command(group, endpoint, output_conf):
headers=not no_headers,
)
)
+ if not pagination:
+ break
pagination_data = get_pagination_data(payload)
if pagination_data["page_size"]:
start = (
@@ -499,17 +536,20 @@ def get_ls_command(group, endpoint, output_conf):
return ls
-def get_show_command(group, url_template, output_conf, name='show', force_id=None):
+def get_show_command(group, url_template, output_conf, name="show", force_id=None):
available_fields = sorted(
set(output_conf["labels"]) | set(output.FIELDS["*"].keys())
)
if force_id:
+
def id_decorator(f):
@functools.wraps(f)
def inner(raw, column, format):
return f(raw, force_id, column, format)
+
return inner
+
else:
id_decorator = click.argument("id")
@@ -529,16 +569,12 @@ def get_show_command(group, url_template, output_conf, name='show', force_id=Non
)
@click.pass_context
@async_command
- async def show(
- ctx,
- raw,
- id,
- column,
- format,
- ):
+ async def show(ctx, raw, id, column, format):
async with ctx.obj["remote"]:
- async with ctx.obj["remote"].request("get", url_template.format(id)) as result:
+ async with ctx.obj["remote"].request(
+ "get", url_template.format(id)
+ ) as result:
result.raise_for_status()
payload = await result.json()
if raw:
@@ -1052,6 +1088,42 @@ async def playlists_create(ctx, raw, name, visibility):
)
+@playlists.command("tracks-add")
+@click.argument("id")
+@click.argument("track", nargs=-1)
+@click.option(
+ "--no-duplicates",
+ is_flag=True,
+ default=False,
+ help="Prevent insertion of tracks that already exist in the playlist",
+)
+@click.pass_context
+@async_command
+async def playlists_tracks_add(ctx, id, track, no_duplicates):
+ async with ctx.obj["remote"]:
+ async with ctx.obj["remote"].request(
+ "post",
+ "api/v1/playlists/{}/".format(id),
+ data={"tracks": track, "allow_duplicates": not no_duplicates},
+ ) as response:
+ response.raise_for_status()
+
+
+playlists_tracks = get_ls_command(
+ playlists,
+ "api/v1/playlists/{}/tracks/",
+ name="tracks",
+ with_id=True,
+ pagination=False,
+ ordering=False,
+ filter=False,
+ output_conf={
+ "labels": ["Position", "ID", "Title", "Artist", "Album", "Created"],
+ "id_field": "ID",
+ "type": "PLAYLIST_TRACK",
+ },
+)
+
@cli.group()
@click.pass_context
@@ -1077,8 +1149,8 @@ users_me = get_show_command(
],
"type": "USER",
},
- force_id='me',
- name='me',
+ force_id="me",
+ name="me",
)
diff --git a/funkwhale_cli/output.py b/funkwhale_cli/output.py
index 21ab2d5044ed7da95a730fb4e1b0e4b1412590dc..0616153f77ca5f18c3185bd84abdbf7d5e0d982d 100644
--- a/funkwhale_cli/output.py
+++ b/funkwhale_cli/output.py
@@ -50,6 +50,12 @@ FIELDS = {
"Tracks Count": {"field": "tracks_count"},
"User": {"field": "user.username"},
},
+ "PLAYLIST_TRACK": {
+ "Position": {"field": "index"},
+ "Title": {"field": "track.title"},
+ "Artist": {"field": "track.artist.name"},
+ "Album": {"field": "track.album.title"},
+ },
"USER": {
"Username": {"field": "username"},
"Federation ID": {"field": "full_username"},
diff --git a/tests/test_cli.py b/tests/test_cli.py
index bcf8764dcacd744b00895b8d43b8c5ac41ecf85c..4f0a1f96d829314e7ee0e442002959ad92e93f43 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -337,3 +337,28 @@ def test_playlists_rm(cli_ctx, session, responses, get_requests):
assert len(get_requests("delete", url + "1/")) == 1
assert len(get_requests("delete", url + "42/")) == 1
+
+
+def test_playlists_tracks_add(cli_ctx, session, responses, get_requests):
+ command = cli.playlists_tracks_add
+ url = "https://test.funkwhale/api/v1/playlists/66/"
+ responses.post(url)
+
+ command.callback(id=66, track=[1, 42], no_duplicates=True, _async_reraise=True)
+
+ requests = get_requests("post", url)
+ assert len(requests) == 1
+ assert requests[0].kwargs["data"] == {"tracks": [1, 42], "allow_duplicates": False}
+
+
+def test_playlists_tracks(cli_ctx, session, responses, get_requests):
+ command = cli.playlists_tracks
+ url = "https://test.funkwhale/api/v1/playlists/66/tracks/"
+ responses.get(url, payload={"results": [], "count": 0})
+
+ command.callback(
+ id=66, raw=False, column=None, format=None, no_headers=False, ids=False
+ )
+
+ requests = get_requests("get", url)
+ assert len(requests) == 1