Skip to content

Commit 5460a18

Browse files
CrisJingjackwener
andauthored
fix(xiaoyuzhou): correct podcast-episodes API endpoint (jackwener#1129)
* fix(xiaoyuzhou): correct podcast-episodes API endpoint The endpoint `/v1/podcast/listEpisode` returns 404. The correct endpoint is `/v1/episode/list` (verified against Xiaoyuzhou iOS app traffic; also matches the `episode-list` implementation in ultrazg/xyz, a widely-used Xiaoyuzhou API wrapper). Additionally, the server requires an `order` field in the request body (returns 400 if omitted). Add `order: 'desc'` so callers get the latest episodes first, matching typical UX for a podcast feed. Before: podcast-episodes -> HTTP 404 for every podcast After: podcast-episodes returns the N most recent episodes Tested against real podcast 626b46ea9cbbf0451cf5a962 (张小珺|商业访谈录) — now returns 140 episodes correctly. * test(xiaoyuzhou): lock podcast episodes endpoint --------- Co-authored-by: jackwener <jakevingoo@gmail.com>
1 parent dc72426 commit 5460a18

2 files changed

Lines changed: 80 additions & 2 deletions

File tree

clis/xiaoyuzhou/podcast-episodes.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ cli({
2020
throw new CliError('INVALID_ARGUMENT', 'limit must be a positive integer', 'Example: --limit 5');
2121
}
2222
const credentials = loadXiaoyuzhouCredentials();
23-
const response = await requestXiaoyuzhouJson('/v1/podcast/listEpisode', {
23+
const response = await requestXiaoyuzhouJson('/v1/episode/list', {
2424
method: 'POST',
25-
body: { pid: args.id, limit: requestedLimit },
25+
body: { pid: args.id, order: 'desc', limit: requestedLimit },
2626
credentials,
2727
});
2828
const episodes = response.data ?? [];
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
2+
import { getRegistry } from '@jackwener/opencli/registry';
3+
4+
const { mockRequestJson, mockLoadCredentials } = vi.hoisted(() => ({
5+
mockRequestJson: vi.fn(),
6+
mockLoadCredentials: vi.fn(),
7+
}));
8+
9+
vi.mock('./auth.js', async () => {
10+
const actual = await vi.importActual('./auth.js');
11+
return {
12+
...actual,
13+
requestXiaoyuzhouJson: mockRequestJson,
14+
loadXiaoyuzhouCredentials: mockLoadCredentials,
15+
};
16+
});
17+
18+
await import('./podcast-episodes.js');
19+
20+
let cmd;
21+
22+
beforeAll(() => {
23+
cmd = getRegistry().get('xiaoyuzhou/podcast-episodes');
24+
expect(cmd?.func).toBeTypeOf('function');
25+
});
26+
27+
describe('xiaoyuzhou podcast-episodes', () => {
28+
beforeEach(() => {
29+
mockRequestJson.mockReset();
30+
mockLoadCredentials.mockReset();
31+
mockLoadCredentials.mockReturnValue({ access_token: 'access', refresh_token: 'refresh' });
32+
});
33+
34+
it('calls the fixed episode list endpoint with desc ordering', async () => {
35+
mockRequestJson.mockResolvedValue({
36+
data: [
37+
{
38+
eid: 'ep-1',
39+
title: 'Episode 1',
40+
duration: 3661,
41+
playCount: 42,
42+
pubDate: '2026-04-20T10:00:00.000Z',
43+
},
44+
],
45+
});
46+
47+
const result = await cmd.func(null, {
48+
id: 'podcast-1',
49+
limit: 3,
50+
});
51+
52+
expect(mockRequestJson).toHaveBeenCalledWith('/v1/episode/list', {
53+
method: 'POST',
54+
body: { pid: 'podcast-1', order: 'desc', limit: 3 },
55+
credentials: { access_token: 'access', refresh_token: 'refresh' },
56+
});
57+
expect(result).toEqual([
58+
{
59+
eid: 'ep-1',
60+
title: 'Episode 1',
61+
duration: '61:01',
62+
plays: 42,
63+
date: '2026-04-20',
64+
},
65+
]);
66+
});
67+
68+
it('rejects non-positive limits before hitting the API', async () => {
69+
await expect(cmd.func(null, {
70+
id: 'podcast-1',
71+
limit: 0,
72+
})).rejects.toMatchObject({
73+
code: 'INVALID_ARGUMENT',
74+
message: 'limit must be a positive integer',
75+
});
76+
expect(mockRequestJson).not.toHaveBeenCalled();
77+
});
78+
});

0 commit comments

Comments
 (0)