All checks were successful
Publish Docker image / Push Docker image to Docker Hub (push) Successful in 1m30s
228 lines
6.6 KiB
JavaScript
228 lines
6.6 KiB
JavaScript
const { google } = require('googleapis');
|
|
const youtube = google.youtube('v3');
|
|
|
|
const YOUTUBE_API_KEY = process.env.YOUTUBE_API_KEY;
|
|
|
|
const PLAYLISTS = {
|
|
seventies: 'PLmXxqSJJq-yXrCPGIT2gn8b34JjOrl4Xf',
|
|
eighties: 'PLmXxqSJJq-yUvMWKuZQAB_8yxnjZaOZUp',
|
|
nineties: 'PLmXxqSJJq-yUF3jbzjF_pa--kuBuMlyQQ',
|
|
pop: 'PLxA687tYuMWhkqYjvAGtW_heiEL4Hk_Lx',
|
|
marco: 'PLSTnYsLCH0WKpUvzrytqfvlhDTlqK_zj-'
|
|
};
|
|
|
|
let validatedPlaylists = {};
|
|
const VALIDATION_TTL = 3600000; // 1 hour
|
|
|
|
const validatePlaylist = async (playlistId) => {
|
|
try {
|
|
const response = await youtube.playlists.list({
|
|
key: API_KEY,
|
|
part: 'snippet,contentDetails',
|
|
id: playlistId
|
|
});
|
|
|
|
if (!response.data.items || response.data.items.length === 0) {
|
|
console.log(`Playlist ${playlistId} not found or empty`);
|
|
return false;
|
|
}
|
|
|
|
validatedPlaylists[playlistId] = {
|
|
timestamp: Date.now(),
|
|
valid: true
|
|
};
|
|
return true;
|
|
} catch (error) {
|
|
console.error(`Failed to validate playlist ${playlistId}:`, error);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const validateAndCleanPlaylists = async () => {
|
|
const now = Date.now();
|
|
const validPlaylistIds = [];
|
|
|
|
for (const [genre, playlistId] of Object.entries(PLAYLISTS)) {
|
|
if (validatedPlaylists[playlistId] &&
|
|
(now - validatedPlaylists[playlistId].timestamp) < VALIDATION_TTL) {
|
|
if (validatedPlaylists[playlistId].valid) {
|
|
validPlaylistIds.push([genre, playlistId]);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
const isValid = await validatePlaylist(playlistId);
|
|
if (isValid) {
|
|
validPlaylistIds.push([genre, playlistId]);
|
|
} else {
|
|
console.log(`Removing invalid playlist: ${genre} (${playlistId})`);
|
|
delete PLAYLISTS[genre];
|
|
}
|
|
}
|
|
|
|
return validPlaylistIds;
|
|
};
|
|
|
|
const API_KEY = process.env.YOUTUBE_API_KEY;
|
|
|
|
const cachedSongsByPlaylist = {};
|
|
|
|
const CACHE_TTL = 3600000; // 1 hour
|
|
|
|
/**
|
|
* Fetches songs from YouTube playlist and returns them
|
|
*/
|
|
const fetchPlaylistSongs = async (playlistId = null) => {
|
|
if (!playlistId) {
|
|
console.warn("No playlist ID provided, using default");
|
|
playlistId = PLAYLISTS.eighties;
|
|
}
|
|
|
|
const now = Date.now();
|
|
if (cachedSongsByPlaylist[playlistId] &&
|
|
cachedSongsByPlaylist[playlistId].songs.length > 0 &&
|
|
(now - cachedSongsByPlaylist[playlistId].timestamp) < CACHE_TTL) {
|
|
console.log(`Using cached songs for playlist ${playlistId}`);
|
|
return cachedSongsByPlaylist[playlistId].songs;
|
|
}
|
|
|
|
try {
|
|
console.log(`Fetching fresh songs from YouTube API for playlist ${playlistId}...`);
|
|
const playlistUrl = `https://www.googleapis.com/youtube/v3/playlistItems?part=snippet,contentDetails&maxResults=50&playlistId=${playlistId}&key=${YOUTUBE_API_KEY}`;
|
|
const response = await fetch(playlistUrl);
|
|
const data = await response.json();
|
|
|
|
if (data.error) {
|
|
console.error("YouTube API error:", data.error);
|
|
throw new Error(data.error.message);
|
|
}
|
|
|
|
if (!data.items || !data.items.length) {
|
|
throw new Error("No songs found in the playlist");
|
|
}
|
|
|
|
const songs = data.items.map((item, index) => ({
|
|
id: index + 1,
|
|
youtubeId: item.contentDetails.videoId,
|
|
title: item.snippet.title,
|
|
artist: item.snippet.videoOwnerChannelTitle || "Unknown Artist",
|
|
coverUrl: item.snippet.thumbnails.high?.url || item.snippet.thumbnails.default?.url,
|
|
playlistId: playlistId
|
|
}));
|
|
|
|
cachedSongsByPlaylist[playlistId] = {
|
|
songs,
|
|
timestamp: now
|
|
};
|
|
|
|
return songs;
|
|
} catch (error) {
|
|
console.error(`Error fetching YouTube playlist ${playlistId}:`, error);
|
|
return cachedSongsByPlaylist[playlistId]?.songs || getDefaultSongs();
|
|
}
|
|
};
|
|
|
|
const getAvailableSongIds = async (playlistId) => {
|
|
const songs = await fetchPlaylistSongs(playlistId);
|
|
return songs.map(song => song.id);
|
|
};
|
|
|
|
async function getPlaylistDetails(availablePlaylists = null) {
|
|
try {
|
|
const playlistsToUse = availablePlaylists || await getRandomPlaylists(3);
|
|
const details = {};
|
|
const validationPromises = [];
|
|
|
|
for (const [genre, playlistId] of Object.entries(playlistsToUse)) {
|
|
validationPromises.push(
|
|
youtube.playlists.list({
|
|
key: API_KEY,
|
|
part: 'snippet,contentDetails',
|
|
id: playlistId
|
|
}).then(response => {
|
|
if (response.data.items?.[0]) {
|
|
const playlist = response.data.items[0];
|
|
details[genre] = {
|
|
id: playlistId,
|
|
title: playlist.snippet.title,
|
|
description: playlist.snippet.description,
|
|
thumbnail: playlist.snippet.thumbnails.maxres || playlist.snippet.thumbnails.high,
|
|
songCount: playlist.contentDetails.itemCount,
|
|
votes: 0
|
|
};
|
|
} else {
|
|
console.warn(`Playlist not found: ${genre} (${playlistId})`);
|
|
}
|
|
}).catch(error => {
|
|
console.error(`Error fetching playlist ${playlistId}:`, error);
|
|
})
|
|
);
|
|
}
|
|
|
|
await Promise.all(validationPromises);
|
|
|
|
if (Object.keys(details).length === 0) {
|
|
const response = await youtube.playlists.list({
|
|
key: API_KEY,
|
|
part: 'snippet,contentDetails',
|
|
id: PLAYLISTS.seventies
|
|
});
|
|
|
|
if (response.data.items?.[0]) {
|
|
const playlist = response.data.items[0];
|
|
details.seventies = {
|
|
id: PLAYLISTS.seventies,
|
|
title: playlist.snippet.title,
|
|
description: playlist.snippet.description,
|
|
thumbnail: playlist.snippet.thumbnails.maxres || playlist.snippet.thumbnails.high,
|
|
songCount: playlist.contentDetails.itemCount,
|
|
votes: 0
|
|
};
|
|
}
|
|
}
|
|
|
|
if (Object.keys(details).length === 0) {
|
|
throw new Error("No valid playlists found");
|
|
}
|
|
|
|
return details;
|
|
} catch (error) {
|
|
console.error('Error fetching playlist details:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
const getRandomPlaylists = async (count = 3) => {
|
|
try {
|
|
const allPlaylists = Object.entries(PLAYLISTS);
|
|
if (allPlaylists.length === 0) {
|
|
throw new Error("No playlists configured");
|
|
}
|
|
|
|
const shuffled = [...allPlaylists].sort(() => Math.random() - 0.5);
|
|
const selected = shuffled.slice(0, Math.min(count, allPlaylists.length));
|
|
|
|
const result = {};
|
|
for (const [genre, playlistId] of selected) {
|
|
result[genre] = playlistId;
|
|
}
|
|
|
|
return result;
|
|
} catch (error) {
|
|
console.error("Error getting random playlists:", error);
|
|
return {
|
|
seventies: PLAYLISTS.seventies
|
|
};
|
|
}
|
|
};
|
|
|
|
module.exports = {
|
|
fetchPlaylistSongs,
|
|
getAvailableSongIds,
|
|
PLAYLISTS,
|
|
getPlaylistDetails,
|
|
getRandomPlaylists,
|
|
validateAndCleanPlaylists,
|
|
validatePlaylist
|
|
};
|