feat(api-server): Add queue api (#2767)

This commit is contained in:
Su-Yong
2024-12-25 18:55:24 +09:00
committed by GitHub
parent 3ea13a2a22
commit 1d6251baea
7 changed files with 407 additions and 26 deletions

View File

@ -5,15 +5,20 @@ import { ipcMain } from 'electron';
import getSongControls from '@/providers/song-controls';
import {
AddSongToQueueSchema,
AuthHeadersSchema,
type ResponseSongInfo,
SongInfoSchema,
SeekSchema,
GoForwardScheme,
GoBackSchema,
SwitchRepeatSchema,
SetVolumeSchema,
GoForwardScheme,
MoveSongInQueueSchema,
QueueParamsSchema,
SearchSchema,
SeekSchema,
SetFullscreenSchema,
SetQueueIndexSchema,
SetVolumeSchema,
SongInfoSchema,
SwitchRepeatSchema,
type ResponseSongInfo,
} from '../scheme';
import type { RepeatMode } from '@/types/datahost-get-state';
@ -22,6 +27,7 @@ import type { BackendContext } from '@/types/contexts';
import type { APIServerConfig } from '../../config';
import type { HonoApp } from '../types';
import type { QueueResponse } from '@/types/youtube-music-desktop-internal';
import type { Context } from 'hono';
const API_VERSION = 'v1';
@ -297,7 +303,8 @@ const routes = {
},
},
}),
queueInfo: createRoute({
oldQueueInfo: createRoute({
deprecated: true,
method: 'get',
path: `/api/${API_VERSION}/queue-info`,
summary: 'get current queue info',
@ -316,7 +323,8 @@ const routes = {
},
},
}),
songInfo: createRoute({
oldSongInfo: createRoute({
deprecated: true,
method: 'get',
path: `/api/${API_VERSION}/song-info`,
summary: 'get current song info',
@ -335,6 +343,164 @@ const routes = {
},
},
}),
songInfo: createRoute({
method: 'get',
path: `/api/${API_VERSION}/song`,
summary: 'get current song info',
description: 'Get the current song info',
responses: {
200: {
description: 'Success',
content: {
'application/json': {
schema: SongInfoSchema,
},
},
},
204: {
description: 'No song info',
},
},
}),
queueInfo: createRoute({
method: 'get',
path: `/api/${API_VERSION}/queue`,
summary: 'get current queue info',
description: 'Get the current queue info',
responses: {
200: {
description: 'Success',
content: {
'application/json': {
schema: z.object({}),
},
},
},
204: {
description: 'No queue info',
},
},
}),
addSongToQueue: createRoute({
method: 'post',
path: `/api/${API_VERSION}/queue`,
summary: 'add song to queue',
description: 'Add a song to the queue',
request: {
headers: AuthHeadersSchema,
body: {
description: 'video id of the song to add',
content: {
'application/json': {
schema: AddSongToQueueSchema,
},
},
},
},
responses: {
204: {
description: 'Success',
},
},
}),
moveSongInQueue: createRoute({
method: 'patch',
path: `/api/${API_VERSION}/queue/{index}`,
summary: 'move song in queue',
description: 'Move a song in the queue',
request: {
headers: AuthHeadersSchema,
params: QueueParamsSchema,
body: {
description: 'index to move the song to',
content: {
'application/json': {
schema: MoveSongInQueueSchema,
},
},
},
},
responses: {
204: {
description: 'Success',
},
},
}),
removeSongFromQueue: createRoute({
method: 'delete',
path: `/api/${API_VERSION}/queue/{index}`,
summary: 'remove song from queue',
description: 'Remove a song from the queue',
request: {
headers: AuthHeadersSchema,
params: QueueParamsSchema,
},
responses: {
204: {
description: 'Success',
},
},
}),
setQueueIndex: createRoute({
method: 'patch',
path: `/api/${API_VERSION}/queue`,
summary: 'set queue index',
description: 'Set the current index of the queue',
request: {
headers: AuthHeadersSchema,
body: {
description: 'index to move the song to',
content: {
'application/json': {
schema: SetQueueIndexSchema,
},
},
},
},
responses: {
204: {
description: 'Success',
},
},
}),
clearQueue: createRoute({
method: 'delete',
path: `/api/${API_VERSION}/queue`,
summary: 'clear queue',
description: 'Clear the queue',
responses: {
204: {
description: 'Success',
},
},
}),
search: createRoute({
method: 'post',
path: `/api/${API_VERSION}/search`,
summary: 'search for a song',
description: 'search for a song',
request: {
headers: AuthHeadersSchema,
body: {
description: 'search query',
content: {
'application/json': {
schema: SearchSchema,
},
},
},
},
responses: {
200: {
description: 'Success',
content: {
'application/json': {
schema: z.object({}),
},
},
},
},
}),
};
export const register = (
@ -464,7 +630,26 @@ export const register = (
ctx.status(200);
return ctx.json({ state: fullscreen });
});
app.openapi(routes.queueInfo, async (ctx) => {
const songInfo = (ctx: Context) => {
const info = songInfoGetter();
if (!info) {
ctx.status(204);
return ctx.body(null);
}
const body = { ...info };
delete body.image;
ctx.status(200);
return ctx.json(body satisfies ResponseSongInfo);
};
app.openapi(routes.oldSongInfo, songInfo);
app.openapi(routes.songInfo, songInfo);
// Queue
const queueInfo = async (ctx: Context) => {
const queueResponsePromise = new Promise<QueueResponse>((resolve) => {
ipcMain.once('ytmd:get-queue-response', (_, queue: QueueResponse) => {
return resolve(queue);
@ -482,19 +667,50 @@ export const register = (
ctx.status(200);
return ctx.json(info);
};
app.openapi(routes.oldQueueInfo, queueInfo);
app.openapi(routes.queueInfo, queueInfo);
app.openapi(routes.addSongToQueue, (ctx) => {
const { videoId } = ctx.req.valid('json');
controller.addSongToQueue(videoId);
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.songInfo, (ctx) => {
const info = songInfoGetter();
app.openapi(routes.moveSongInQueue, (ctx) => {
const index = Number(ctx.req.param('index'));
const { toIndex } = ctx.req.valid('json');
controller.moveSongInQueue(index, toIndex);
if (!info) {
ctx.status(204);
return ctx.body(null);
}
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.removeSongFromQueue, (ctx) => {
const index = Number(ctx.req.param('index'));
controller.removeSongFromQueue(index);
const body = { ...info };
delete body.image;
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.setQueueIndex, (ctx) => {
const { index } = ctx.req.valid('json');
controller.setQueueIndex(index);
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.clearQueue, (ctx) => {
controller.clearQueue();
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.search, async (ctx) => {
const { query } = ctx.req.valid('json');
const response = await controller.search(query);
ctx.status(200);
return ctx.json(body satisfies ResponseSongInfo);
return ctx.json(response as object);
});
};

View File

@ -6,3 +6,5 @@ export * from './go-forward';
export * from './switch-repeat';
export * from './set-volume';
export * from './set-fullscreen';
export * from './queue';
export * from './search';

View File

@ -0,0 +1,15 @@
import { z } from '@hono/zod-openapi';
export const QueueParamsSchema = z.object({
index: z.coerce.number().int().nonnegative(),
});
export const AddSongToQueueSchema = z.object({
videoId: z.string(),
});
export const MoveSongInQueueSchema = z.object({
toIndex: z.number(),
});
export const SetQueueIndexSchema = z.object({
index: z.number().int().nonnegative(),
});

View File

@ -0,0 +1,5 @@
import { z } from '@hono/zod-openapi';
export const SearchSchema = z.object({
query: z.string(),
});