Skip to content
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@foobar404/wave": "2.0.5",
"@ghostery/adblocker-electron": "2.18.0",
"@ghostery/adblocker-electron-preload": "2.18.0",
"@hono/mcp": "0.3.1",
"@hono/node-server": "2.0.6",
"@hono/swagger-ui": "0.6.1",
"@hono/zod-openapi": "1.4.0",
Expand All @@ -67,6 +68,7 @@
"@jellybrick/electron-better-web-request": "2.0.0",
"@jellybrick/mpris-service": "2.2.0",
"@mdui/icons": "1.0.3",
"@modelcontextprotocol/sdk": "1.30.0",
"@skyra/jaro-winkler": "1.1.1",
"@xhayper/discord-rpc": "1.3.4",
"async-mutex": "0.5.0",
Expand Down
869 changes: 701 additions & 168 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion src/plugins/api-server/backend/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import { registerCallback } from '@/providers/song-info';
import { createBackend } from '@/utils';

import { API_VERSION } from './api-version';
import { registerAuth, registerControl, registerWebsocket } from './routes';
import {
registerAuth,
registerControl,
registerMcp,
registerWebsocket,
} from './routes';
import { JWTPayloadSchema } from './scheme';

import { type APIServerConfig, AuthStrategy } from '../config';
Expand Down Expand Up @@ -138,6 +143,7 @@ export const backend = createBackend<BackendType, APIServerConfig>({
) as Promise<LikeType>,
() => this.volumeState,
);
registerMcp(this.app, backendCtx, () => this.songInfo);
registerAuth(this.app, backendCtx);
registerWebsocket(this.app, backendCtx, upgradeWebSocket);

Expand Down
67 changes: 35 additions & 32 deletions src/plugins/api-server/backend/routes/control.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { randomUUID } from 'node:crypto';

import { createRoute, z } from '@hono/zod-openapi';
import { ipcMain } from 'electron';

Expand All @@ -8,6 +10,8 @@ import {
type VolumeState,
} from '@/types/datahost-get-state';

import { getQueueItemRenderer, getSelectedQueueIndex } from './queue';

import { API_VERSION } from '../api-version';
import {
AddSongToQueueSchema,
Expand Down Expand Up @@ -568,6 +572,7 @@ const routes = {
};

type PromiseOrValue<T> = T | Promise<T>;
const QUEUE_RESPONSE_TIMEOUT_MS = 5_000;

export const register = (
app: HonoApp,
Expand All @@ -578,6 +583,26 @@ export const register = (
volumeStateGetter: () => PromiseOrValue<VolumeState | undefined>,
) => {
const controller = getSongControls(window);
const getQueueResponse = () =>
new Promise<QueueResponse | undefined>((resolve) => {
const requestId = randomUUID();
const event = 'peard:get-queue-response';
let timeout: NodeJS.Timeout;
const listener = (_: Electron.IpcMainEvent, queue: QueueResponse) => {
if (queue.requestId !== requestId) return;

clearTimeout(timeout);
ipcMain.removeListener(event, listener);
resolve(queue);
};

ipcMain.on(event, listener);
timeout = setTimeout(() => {
ipcMain.removeListener(event, listener);
resolve(undefined);
}, QUEUE_RESPONSE_TIMEOUT_MS);
controller.requestQueueInformation(requestId);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

app.openapi(routes.previous, (ctx) => {
controller.previous();
Expand Down Expand Up @@ -747,51 +772,32 @@ export const register = (

// Queue
const queueInfo = async (ctx: Context) => {
const queueResponsePromise = new Promise<QueueResponse>((resolve) => {
ipcMain.once('peard:get-queue-response', (_, queue: QueueResponse) => {
return resolve(queue);
});

controller.requestQueueInformation();
});

const info = await queueResponsePromise;
const info = await getQueueResponse();

if (!info) {
if (!info?.items) {
ctx.status(204);
return ctx.body(null);
}

ctx.status(200);
return ctx.json(info);
return ctx.json({
items: info.items,
autoPlaying: info.autoPlaying,
continuation: info.continuation,
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
};
app.openapi(routes.oldQueueInfo, queueInfo);
app.openapi(routes.queueInfo, queueInfo);

app.openapi(routes.nextSongInfo, async (ctx) => {
const queueResponsePromise = new Promise<QueueResponse>((resolve) => {
ipcMain.once('peard:get-queue-response', (_, queue: QueueResponse) => {
return resolve(queue);
});

controller.requestQueueInformation();
});

const queue = await queueResponsePromise;
const queue = await getQueueResponse();

if (!queue?.items || queue.items.length === 0) {
ctx.status(204);
return ctx.body(null);
}

// Find the currently selected song
const currentIndex = queue.items.findIndex((item) => {
const renderer =
item.playlistPanelVideoRenderer ||
item.playlistPanelVideoWrapperRenderer?.primaryRenderer
?.playlistPanelVideoRenderer;
return renderer?.selected === true;
});
const currentIndex = getSelectedQueueIndex(queue);

// Get the next song (currentIndex + 1)
const nextIndex = currentIndex + 1;
Expand All @@ -802,10 +808,7 @@ export const register = (
}

const nextItem = queue.items[nextIndex];
const nextRenderer =
nextItem.playlistPanelVideoRenderer ||
nextItem.playlistPanelVideoWrapperRenderer?.primaryRenderer
?.playlistPanelVideoRenderer;
const nextRenderer = getQueueItemRenderer(nextItem);

if (!nextRenderer) {
ctx.status(204);
Expand Down
1 change: 1 addition & 0 deletions src/plugins/api-server/backend/routes/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { register as registerControl } from './control';
export { register as registerAuth } from './auth';
export { register as registerMcp } from './mcp';
export { register as registerWebsocket } from './websocket';
Loading
Loading