Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,41 @@ def fake_run_client_action(ctx, action):
assert called["kwargs"]["xsec_token"] == "token-abc"
assert called["kwargs"]["xsec_source"] == "pc_search"

def test_sub_comments_passes_xsec_token(self, monkeypatch):
called = {}

class FakeClient:
def get_sub_comments(self, note_id, comment_id, cursor="", xsec_token=""):
called["note_id"] = note_id
called["comment_id"] = comment_id
called["cursor"] = cursor
called["xsec_token"] = xsec_token
return {"comments": []}

def fake_handle_command(ctx, action, render, as_json, as_yaml):
action(FakeClient())
return None

monkeypatch.setattr("xhs_cli.commands.reading.handle_command", fake_handle_command)

result = runner.invoke(cli, [
"sub-comments",
"note-123",
"comment-456",
"--cursor",
"cursor-789",
"--xsec-token",
"synthetic-token",
])

assert result.exit_code == 0
assert called == {
"note_id": "note-123",
"comment_id": "comment-456",
"cursor": "cursor-789",
"xsec_token": "synthetic-token",
}

def test_read_index_not_found_returns_usage_error(self, monkeypatch):
monkeypatch.setattr("xhs_cli.note_refs.get_note_by_index", lambda idx: None)

Expand Down
35 changes: 35 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,41 @@ def close(self):


class TestReadingEndpointBehavior:
def test_get_sub_comments_sends_browser_query_parameters(self, monkeypatch):
captured = {}

def fake_get(self, uri, params=None):
captured["uri"] = uri
captured["params"] = params
return {"comments": []}

monkeypatch.setattr(XhsClient, "_main_api_get", fake_get)

client = XhsClient({"a1": "cookie"})
try:
result = client.get_sub_comments(
"note-123",
"comment-456",
cursor="cursor-789",
xsec_token="synthetic-token",
)
finally:
client.close()

assert result == {"comments": []}
assert captured == {
"uri": "/api/sns/web/v2/comment/sub/page",
"params": {
"note_id": "note-123",
"root_comment_id": "comment-456",
"num": 30,
"cursor": "cursor-789",
"image_formats": "jpg,webp,avif",
"top_comment_id": "",
"xsec_token": "synthetic-token",
},
}

def test_get_note_detail_prefers_cached_xsec_source(self, monkeypatch):
captured = {}

Expand Down
4 changes: 4 additions & 0 deletions xhs_cli/client_mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,12 +472,16 @@ def get_sub_comments(
root_comment_id: str,
num: int = 30,
cursor: str = "",
xsec_token: str = "",
) -> Any:
return self._main_api_get("/api/sns/web/v2/comment/sub/page", {
"note_id": note_id,
"root_comment_id": root_comment_id,
"num": num,
"cursor": cursor,
"image_formats": "jpg,webp,avif",
"top_comment_id": "",
"xsec_token": xsec_token,
})


Expand Down
18 changes: 16 additions & 2 deletions xhs_cli/commands/reading.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,13 +229,27 @@ def topics(ctx, keyword: str, as_json: bool, as_yaml: bool):
@click.argument("note_id")
@click.argument("comment_id")
@click.option("--cursor", default="", help="Pagination cursor")
@click.option("--xsec-token", default="", help="Security token")
@structured_output_options
@click.pass_context
def sub_comments(ctx, note_id: str, comment_id: str, cursor: str, as_json: bool, as_yaml: bool):
def sub_comments(
ctx,
note_id: str,
comment_id: str,
cursor: str,
xsec_token: str,
as_json: bool,
as_yaml: bool,
):
"""View replies to a specific comment."""
handle_command(
ctx,
action=lambda client: client.get_sub_comments(note_id, comment_id, cursor=cursor),
action=lambda client: client.get_sub_comments(
note_id,
comment_id,
cursor=cursor,
xsec_token=xsec_token,
),
render=render_comments,
as_json=as_json,
as_yaml=as_yaml,
Expand Down