feat(api-server): add endpoint to play playlists#4505
Conversation
📝 WalkthroughWalkthroughAdds a ChangesPlaylist playback endpoint
Sequence Diagram(s)sequenceDiagram
participant APIClient as API client
participant PlayPlaylistRoute as POST /api/{API_VERSION}/playPlaylist
participant Controller as controller.playPlaylist
participant WebContents as win.webContents
APIClient->>PlayPlaylistRoute: JSON body { playlistId, videoId? }
PlayPlaylistRoute->>PlayPlaylistRoute: validate request body
PlayPlaylistRoute->>Controller: playPlaylist(playlistId, videoId)
Controller->>WebContents: loadURL(music.youtube.com/playlist...)
PlayPlaylistRoute-->>APIClient: 204 No Content
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/plugins/api-server/backend/routes/control.ts`:
- Around line 69-81: The playPlaylist request schema in control.ts currently
uses z.string() for playlistId, which allows blank or whitespace-only IDs and
can lead to a false 204 success; update the createRoute body schema to validate
a trimmed, non-empty playlistId (and keep videoId aligned if needed) so invalid
requests are rejected before reaching the controller logic.
In `@src/providers/song-controls.ts`:
- Around line 149-157: `playPlaylist` in `song-controls.ts` currently ignores
the `win.webContents.loadURL(url)` promise, so navigation failures are unhandled
and the API route can respond too early. Update `playPlaylist` to return the
`loadURL` promise (or otherwise await it), and make the control route in
`control.ts` asynchronous so it awaits `playPlaylist` before sending the 204
response. Use the existing `playPlaylist` method and the route handler that
calls it to keep error reporting and completion behavior aligned.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f4670fd7-f06d-40e5-a46b-ee3fd04520f1
📒 Files selected for processing (3)
.github/workflows/build.ymlsrc/plugins/api-server/backend/routes/control.tssrc/providers/song-controls.ts
| playPlaylist: createRoute({ | ||
| method: 'post', | ||
| path: `/api/${API_VERSION}/playPlaylist`, | ||
| summary: 'play playlist', | ||
| description: 'Plays a playlist given its ID and optionally a video ID', | ||
| request: { | ||
| body: { | ||
| content: { | ||
| 'application/json': { | ||
| schema: z.object({ | ||
| playlistId: z.string().describe('Playlist ID'), | ||
| videoId: z.string().optional().describe('Video ID (optional)'), | ||
| }), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Read the specific lines for the schema definition
echo "=== Schema Definition (lines 69-81) ==="
sed -n '69,90p' src/plugins/api-server/backend/routes/control.ts
# Read the controller implementation for playPlaylist
echo "=== playPlaylist Controller Implementation ==="
rg -A 20 "async playPlaylist" src/providers/song-controls.ts
# Check if there are any existing validation tests
echo "=== Look for validation tests ==="
find . -type f -name "*.test.ts" -o -name "*.spec.ts" | head -5Repository: pear-devs/pear-desktop
Length of output: 1236
🏁 Script executed:
#!/bin/bash
# Search for playPlaylist function in song-controls.ts more broadly
echo "=== Searching for playPlaylist in song-controls.ts ==="
grep -n "playPlaylist" src/providers/song-controls.ts
# If found, show the function definition
if grep -q "playPlaylist" src/providers/song-controls.ts; then
echo "=== playPlaylist Function Context ==="
grep -B 5 -A 30 "playPlaylist" src/providers/song-controls.ts
fi
# Verify Zod behavior with a quick script
echo "=== Verifying Zod behavior with empty strings ==="
node - <<'JS'
const { z } = require('zod');
const schema = z.object({
playlistId: z.string().describe('Playlist ID'),
videoId: z.string().optional().describe('Video ID (optional)'),
});
const testCases = [
{ playlistId: '', videoId: undefined },
{ playlistId: ' ', videoId: 'vid123' },
{ playlistId: 'valid-id', videoId: '' },
{ playlistId: 'valid-id', videoId: ' ' }
];
testCases.forEach((data, i) => {
try {
const result = schema.parse(data);
console.log(`Test ${i+1} PASSED:`, JSON.stringify(data), '->', JSON.stringify(result));
} catch (e) {
console.log(`Test ${i+1} FAILED:`, JSON.stringify(data), '->', e.errors.map(e => e.message).join(', '));
}
});
JSRepository: pear-devs/pear-desktop
Length of output: 1354
Reject blank IDs in the request schema.
z.string() accepts empty strings '' and whitespace-only values. The controller logic silently skips execution if a blank playlistId is provided (due to falsy check) while the endpoint still returns 204 Success. This masks client errors.
Update the schema to enforce non-empty, trimmed strings:
Suggested change
request: {
body: {
content: {
'application/json': {
schema: z.object({
- playlistId: z.string().describe('Playlist ID'),
- videoId: z.string().optional().describe('Video ID (optional)'),
+ playlistId: z.string().trim().min(1).describe('Playlist ID'),
+ videoId: z.string().trim().min(1).optional().describe('Video ID (optional)'),
}),
},
},
},
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| playPlaylist: createRoute({ | |
| method: 'post', | |
| path: `/api/${API_VERSION}/playPlaylist`, | |
| summary: 'play playlist', | |
| description: 'Plays a playlist given its ID and optionally a video ID', | |
| request: { | |
| body: { | |
| content: { | |
| 'application/json': { | |
| schema: z.object({ | |
| playlistId: z.string().describe('Playlist ID'), | |
| videoId: z.string().optional().describe('Video ID (optional)'), | |
| }), | |
| playPlaylist: createRoute({ | |
| method: 'post', | |
| path: `/api/${API_VERSION}/playPlaylist`, | |
| summary: 'play playlist', | |
| description: 'Plays a playlist given its ID and optionally a video ID', | |
| request: { | |
| body: { | |
| content: { | |
| 'application/json': { | |
| schema: z.object({ | |
| playlistId: z.string().trim().min(1).describe('Playlist ID'), | |
| videoId: z.string().trim().min(1).optional().describe('Video ID (optional)'), | |
| }), |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/plugins/api-server/backend/routes/control.ts` around lines 69 - 81, The
playPlaylist request schema in control.ts currently uses z.string() for
playlistId, which allows blank or whitespace-only IDs and can lead to a false
204 success; update the createRoute body schema to validate a trimmed, non-empty
playlistId (and keep videoId aligned if needed) so invalid requests are rejected
before reaching the controller logic.
| playPlaylist: (playlistId: ArgsType<string>, videoId?: ArgsType<string>) => { | ||
| const pid = parseStringFromArgsType(playlistId); | ||
| const vid = videoId ? parseStringFromArgsType(videoId) : null; | ||
| if (pid) { | ||
| let url = `https://music.youtube.com/playlist?list=${pid}`; | ||
| if (vid) { | ||
| url += `&v=${vid}`; | ||
| } | ||
| win.webContents.loadURL(url); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major
Handle loadURL() promise to ensure navigation completion and error reporting.
win.webContents.loadURL(url) returns a Promise<void> that rejects on navigation failure. The current playPlaylist implementation drops this promise, and the route handler in src/plugins/api-server/backend/routes/control.ts (lines 623–629) is synchronous, returning HTTP 204 before navigation completes. This results in unhandled navigation errors and premature success responses.
Update playPlaylist to return the promise and make the route handler asynchronous to await it:
Suggested change
- playPlaylist: (playlistId: ArgsType<string>, videoId?: ArgsType<string>) => {
+ playPlaylist: async (playlistId: ArgsType<string>, videoId?: ArgsType<string>) => {
const pid = parseStringFromArgsType(playlistId);
const vid = videoId ? parseStringFromArgsType(videoId) : null;
if (pid) {
let url = `https://music.youtube.com/playlist?list=${pid}`;
if (vid) {
url += `&v=${vid}`;
}
- win.webContents.loadURL(url);
+ return win.webContents.loadURL(url);
}
},- app.openapi(routes.playPlaylist, (ctx) => {
+ app.openapi(routes.playPlaylist, async (ctx) => {
const { playlistId, videoId } = ctx.req.valid('json');
- controller.playPlaylist(playlistId, videoId);
+ await controller.playPlaylist(playlistId, videoId);
ctx.status(204);
return ctx.body(null);
});🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/providers/song-controls.ts` around lines 149 - 157, `playPlaylist` in
`song-controls.ts` currently ignores the `win.webContents.loadURL(url)` promise,
so navigation failures are unhandled and the API route can respond too early.
Update `playPlaylist` to return the `loadURL` promise (or otherwise await it),
and make the control route in `control.ts` asynchronous so it awaits
`playPlaylist` before sending the 204 response. Use the existing `playPlaylist`
method and the route handler that calls it to keep error reporting and
completion behavior aligned.
This PR adds a new
/api/v1/playPlaylistendpoint to theapi-serverplugin.Currently, third-party apps (like Macro Deck) can skip tracks and pause, but they can't launch a specific playlist.
This update allows external controllers to pass a
playlistId(and optionalvideoId) to instantly load and play a playlist.Summary by CodeRabbit
New Features
Bug Fixes