mirror of
https://github.com/svemagie/indiekit-endpoint-youtube.git
synced 2026-04-02 15:54:59 +02:00
The Indiekit backend UI was broken when using channels array because the dashboard controller only checked for channelId/channelHandle. Now uses getPrimaryChannel() helper to extract the first channel from either single-channel or multi-channel configuration. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
113 lines
3.6 KiB
JavaScript
113 lines
3.6 KiB
JavaScript
import { YouTubeClient } from "../youtube-client.js";
|
|
import { getChannelsFromConfig } from "./channel.js";
|
|
|
|
/**
|
|
* Live status controller
|
|
*/
|
|
export const liveController = {
|
|
/**
|
|
* Get live status (JSON API)
|
|
* Uses efficient method (checking recent videos) by default
|
|
* Use ?full=true for full search (costs 100 quota units)
|
|
* Returns live status for all configured channels
|
|
* @type {import("express").RequestHandler}
|
|
*/
|
|
async api(request, response) {
|
|
try {
|
|
const { youtubeConfig } = request.app.locals.application;
|
|
|
|
if (!youtubeConfig) {
|
|
return response.status(500).json({ error: "Not configured" });
|
|
}
|
|
|
|
const { apiKey, liveCacheTtl } = youtubeConfig;
|
|
const channelConfigs = getChannelsFromConfig(youtubeConfig);
|
|
|
|
if (!apiKey || channelConfigs.length === 0) {
|
|
return response.status(500).json({ error: "Invalid configuration" });
|
|
}
|
|
|
|
const useFullSearch = request.query.full === "true";
|
|
|
|
// Fetch live status from all channels in parallel
|
|
const livePromises = channelConfigs.map(async (channelConfig) => {
|
|
const client = new YouTubeClient({
|
|
apiKey,
|
|
channelId: channelConfig.id,
|
|
channelHandle: channelConfig.handle,
|
|
liveCacheTtl,
|
|
});
|
|
|
|
try {
|
|
const liveStatus = useFullSearch
|
|
? await client.getLiveStatus()
|
|
: await client.getLiveStatusEfficient();
|
|
|
|
if (liveStatus) {
|
|
return {
|
|
channelConfigName: channelConfig.name,
|
|
isLive: liveStatus.isLive || false,
|
|
isUpcoming: liveStatus.isUpcoming || false,
|
|
stream: {
|
|
videoId: liveStatus.videoId,
|
|
title: liveStatus.title,
|
|
thumbnail: liveStatus.thumbnail,
|
|
url: `https://www.youtube.com/watch?v=${liveStatus.videoId}`,
|
|
scheduledStart: liveStatus.scheduledStart,
|
|
actualStart: liveStatus.actualStart,
|
|
},
|
|
};
|
|
}
|
|
return {
|
|
channelConfigName: channelConfig.name,
|
|
isLive: false,
|
|
isUpcoming: false,
|
|
stream: null,
|
|
};
|
|
} catch (error) {
|
|
console.error(
|
|
`[YouTube] Failed to fetch live status for ${channelConfig.name || channelConfig.handle}:`,
|
|
error.message
|
|
);
|
|
return {
|
|
channelConfigName: channelConfig.name,
|
|
isLive: false,
|
|
isUpcoming: false,
|
|
stream: null,
|
|
};
|
|
}
|
|
});
|
|
|
|
const liveStatuses = await Promise.all(livePromises);
|
|
|
|
// For single channel, return flat response (backward compatible)
|
|
if (channelConfigs.length === 1) {
|
|
const status = liveStatuses[0];
|
|
response.json({
|
|
isLive: status.isLive,
|
|
isUpcoming: status.isUpcoming,
|
|
stream: status.stream,
|
|
cached: true,
|
|
});
|
|
} else {
|
|
// For multiple channels, find any that are live
|
|
const anyLive = liveStatuses.find((s) => s.isLive);
|
|
const anyUpcoming = liveStatuses.find((s) => s.isUpcoming && !s.isLive);
|
|
|
|
response.json({
|
|
// Backward compat: primary live status (prefer live over upcoming)
|
|
isLive: !!anyLive,
|
|
isUpcoming: !anyLive && !!anyUpcoming,
|
|
stream: anyLive?.stream || anyUpcoming?.stream || null,
|
|
// Multi-channel data
|
|
liveStatuses,
|
|
cached: true,
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error("[YouTube] Live API error:", error);
|
|
response.status(500).json({ error: error.message });
|
|
}
|
|
},
|
|
};
|