Add playlists
This commit is contained in:
parent
195980032c
commit
9901c1a49e
@ -1,3 +1,5 @@
|
|||||||
|
const youtubeService = require('../services/youtubeService');
|
||||||
|
|
||||||
let cleanupGameState;
|
let cleanupGameState;
|
||||||
|
|
||||||
const setCleanupGameState = (cleanupFunction) => {
|
const setCleanupGameState = (cleanupFunction) => {
|
||||||
@ -10,25 +12,41 @@ module.exports.roomExists = (roomId) => rooms[roomId] !== undefined;
|
|||||||
|
|
||||||
module.exports.isRoomOpen = (roomId) => rooms[roomId] && rooms[roomId].state === 'waiting';
|
module.exports.isRoomOpen = (roomId) => rooms[roomId] && rooms[roomId].state === 'waiting';
|
||||||
|
|
||||||
const initializeRoom = (roomId, user) => {
|
const initializeRoom = async (roomId, user) => {
|
||||||
rooms[roomId] = {
|
try {
|
||||||
members: [{...user, creator: true}],
|
const randomPlaylists = await youtubeService.getRandomPlaylists(3);
|
||||||
settings: {},
|
rooms[roomId] = {
|
||||||
state: 'waiting',
|
members: [{...user, creator: true}],
|
||||||
playlistVotes: {},
|
settings: {},
|
||||||
selectedPlaylist: null
|
state: 'waiting',
|
||||||
};
|
playlistVotes: {},
|
||||||
|
selectedPlaylist: null,
|
||||||
|
availablePlaylists: randomPlaylists
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error initializing room playlists:", error);
|
||||||
|
rooms[roomId] = {
|
||||||
|
members: [{...user, creator: true}],
|
||||||
|
settings: {},
|
||||||
|
state: 'waiting',
|
||||||
|
playlistVotes: {},
|
||||||
|
selectedPlaylist: null,
|
||||||
|
availablePlaylists: {
|
||||||
|
seventies: youtubeService.PLAYLISTS.seventies
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.connectUserToRoom = (roomId, user) => {
|
module.exports.connectUserToRoom = async (roomId, user) => {
|
||||||
roomId = roomId.toUpperCase();
|
roomId = roomId.toUpperCase();
|
||||||
if (rooms[roomId]) {
|
if (rooms[roomId]) {
|
||||||
rooms[roomId].members.push({...user, creator: false});
|
rooms[roomId].members.push({...user, creator: false});
|
||||||
} else {
|
} else {
|
||||||
initializeRoom(roomId, user);
|
await initializeRoom(roomId, user);
|
||||||
}
|
}
|
||||||
console.log(`User ${user.name} connected to room ${roomId}`);
|
console.log(`User ${user.name} connected to room ${roomId}`);
|
||||||
}
|
};
|
||||||
|
|
||||||
module.exports.getUserRoom = (userId) => {
|
module.exports.getUserRoom = (userId) => {
|
||||||
for (const roomId in rooms) {
|
for (const roomId in rooms) {
|
||||||
@ -88,7 +106,6 @@ module.exports.disconnectUser = (userId) => {
|
|||||||
|
|
||||||
if (memberIndex !== -1) {
|
if (memberIndex !== -1) {
|
||||||
if (room.members[memberIndex].creator && room.members.length > 1) {
|
if (room.members[memberIndex].creator && room.members.length > 1) {
|
||||||
// Transfer host status to the next user
|
|
||||||
room.members[1].creator = true;
|
room.members[1].creator = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,8 +162,7 @@ module.exports.voteForPlaylist = (roomId, userId, playlistId) => {
|
|||||||
|
|
||||||
const room = rooms[roomId];
|
const room = rooms[roomId];
|
||||||
if (room.state !== 'waiting') return false;
|
if (room.state !== 'waiting') return false;
|
||||||
|
|
||||||
// Remove previous vote if exists
|
|
||||||
const previousVote = Object.entries(room.playlistVotes)
|
const previousVote = Object.entries(room.playlistVotes)
|
||||||
.find(([_, voters]) => voters.includes(userId));
|
.find(([_, voters]) => voters.includes(userId));
|
||||||
|
|
||||||
@ -154,8 +170,7 @@ module.exports.voteForPlaylist = (roomId, userId, playlistId) => {
|
|||||||
room.playlistVotes[previousVote[0]] =
|
room.playlistVotes[previousVote[0]] =
|
||||||
room.playlistVotes[previousVote[0]].filter(id => id !== userId);
|
room.playlistVotes[previousVote[0]].filter(id => id !== userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new vote
|
|
||||||
if (!room.playlistVotes[playlistId]) {
|
if (!room.playlistVotes[playlistId]) {
|
||||||
room.playlistVotes[playlistId] = [];
|
room.playlistVotes[playlistId] = [];
|
||||||
}
|
}
|
||||||
@ -172,7 +187,7 @@ module.exports.getWinningPlaylist = (roomId) => {
|
|||||||
const room = rooms[roomId];
|
const room = rooms[roomId];
|
||||||
if (!room || !room.playlistVotes) {
|
if (!room || !room.playlistVotes) {
|
||||||
console.log(`No votes found for room ${roomId}, using default playlist`);
|
console.log(`No votes found for room ${roomId}, using default playlist`);
|
||||||
return Object.values(require('../services/youtubeService').PLAYLISTS)[0];
|
return room.availablePlaylists[Object.keys(room.availablePlaylists)[0]];
|
||||||
}
|
}
|
||||||
|
|
||||||
let maxVotes = 0;
|
let maxVotes = 0;
|
||||||
@ -190,10 +205,23 @@ module.exports.getWinningPlaylist = (roomId) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!winningPlaylist) {
|
if (!winningPlaylist) {
|
||||||
console.log('No winning playlist found, using default');
|
console.log('No winning playlist found, using first available');
|
||||||
winningPlaylist = Object.values(require('../services/youtubeService').PLAYLISTS)[0];
|
winningPlaylist = room.availablePlaylists[Object.keys(room.availablePlaylists)[0]];
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Selected winning playlist: ${winningPlaylist}`);
|
console.log(`Selected winning playlist: ${winningPlaylist}`);
|
||||||
return winningPlaylist;
|
return winningPlaylist;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.getAvailablePlaylists = (roomId) => {
|
||||||
|
return rooms[roomId]?.availablePlaylists || {};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.updateAvailablePlaylists = (roomId, newPlaylists) => {
|
||||||
|
if (rooms[roomId]) {
|
||||||
|
rooms[roomId].availablePlaylists = newPlaylists;
|
||||||
|
rooms[roomId].playlistVotes = {};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
@ -320,11 +320,29 @@ module.exports = (io) => (socket) => {
|
|||||||
|
|
||||||
socket.on("get-playlist-options", async () => {
|
socket.on("get-playlist-options", async () => {
|
||||||
try {
|
try {
|
||||||
const playlists = await youtubeService.getPlaylistDetails();
|
const roomId = roomController.getUserRoom(socket.id);
|
||||||
socket.emit("playlist-options", playlists);
|
if (!roomId) {
|
||||||
|
throw new Error("User not in a room");
|
||||||
|
}
|
||||||
|
|
||||||
|
const availablePlaylists = roomController.getAvailablePlaylists(roomId);
|
||||||
|
const details = await youtubeService.getPlaylistDetails(availablePlaylists);
|
||||||
|
|
||||||
|
if (Object.keys(details).length === 0) {
|
||||||
|
const newPlaylists = await youtubeService.getRandomPlaylists(3);
|
||||||
|
const newDetails = await youtubeService.getPlaylistDetails(newPlaylists);
|
||||||
|
|
||||||
|
roomController.updateAvailablePlaylists(roomId, newPlaylists);
|
||||||
|
socket.emit("playlist-options", newDetails);
|
||||||
|
} else {
|
||||||
|
socket.emit("playlist-options", details);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching playlist options:", error);
|
console.error("Error fetching playlist options:", error);
|
||||||
socket.emit("error", { message: "Failed to load playlists" });
|
socket.emit("error", {
|
||||||
|
message: "Failed to load playlists",
|
||||||
|
details: error.message
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2,12 +2,65 @@ const { google } = require('googleapis');
|
|||||||
const youtube = google.youtube('v3');
|
const youtube = google.youtube('v3');
|
||||||
|
|
||||||
const YOUTUBE_API_KEY = process.env.YOUTUBE_API_KEY;
|
const YOUTUBE_API_KEY = process.env.YOUTUBE_API_KEY;
|
||||||
const PLAYLIST_ID = "PLmXxqSJJq-yXrCPGIT2gn8b34JjOrl4Xf";
|
|
||||||
|
|
||||||
const PLAYLISTS = {
|
const PLAYLISTS = {
|
||||||
seventies: 'PLmXxqSJJq-yXrCPGIT2gn8b34JjOrl4Xf',
|
seventies: 'PLmXxqSJJq-yXrCPGIT2gn8b34JjOrl4Xf',
|
||||||
eighties: 'PLmXxqSJJq-yUvMWKuZQAB_8yxnjZaOZUp',
|
eighties: 'PLmXxqSJJq-yUvMWKuZQAB_8yxnjZaOZUp',
|
||||||
nineties: 'PLmXxqSJJq-yUF3jbzjF_pa--kuBuMlyQQ'
|
nineties: 'PLmXxqSJJq-yUF3jbzjF_pa--kuBuMlyQQ',
|
||||||
|
pop: 'PLxA687tYuMWhkqYjvAGtW_heiEL4Hk_Lx',
|
||||||
|
dance: 'PL64E6BD94546734D8'
|
||||||
|
};
|
||||||
|
|
||||||
|
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 API_KEY = process.env.YOUTUBE_API_KEY;
|
||||||
@ -22,7 +75,7 @@ const CACHE_TTL = 3600000; // 1 hour
|
|||||||
const fetchPlaylistSongs = async (playlistId = null) => {
|
const fetchPlaylistSongs = async (playlistId = null) => {
|
||||||
if (!playlistId) {
|
if (!playlistId) {
|
||||||
console.warn("No playlist ID provided, using default");
|
console.warn("No playlist ID provided, using default");
|
||||||
playlistId = PLAYLISTS.seventies; // default fallback
|
playlistId = PLAYLISTS.eighties;
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@ -74,28 +127,64 @@ const getAvailableSongIds = async (playlistId) => {
|
|||||||
return songs.map(song => song.id);
|
return songs.map(song => song.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getPlaylistDetails() {
|
async function getPlaylistDetails(availablePlaylists = null) {
|
||||||
try {
|
try {
|
||||||
|
const playlistsToUse = availablePlaylists || await getRandomPlaylists(3);
|
||||||
const details = {};
|
const details = {};
|
||||||
|
const validationPromises = [];
|
||||||
for (const [genre, playlistId] of Object.entries(PLAYLISTS)) {
|
|
||||||
|
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({
|
const response = await youtube.playlists.list({
|
||||||
key: API_KEY,
|
key: API_KEY,
|
||||||
part: 'snippet,contentDetails',
|
part: 'snippet,contentDetails',
|
||||||
id: playlistId
|
id: PLAYLISTS.seventies
|
||||||
});
|
});
|
||||||
|
|
||||||
const playlist = response.data.items[0];
|
if (response.data.items?.[0]) {
|
||||||
details[genre] = {
|
const playlist = response.data.items[0];
|
||||||
id: playlistId,
|
details.seventies = {
|
||||||
title: playlist.snippet.title,
|
id: PLAYLISTS.seventies,
|
||||||
description: playlist.snippet.description,
|
title: playlist.snippet.title,
|
||||||
thumbnail: playlist.snippet.thumbnails.maxres || playlist.snippet.thumbnails.high,
|
description: playlist.snippet.description,
|
||||||
songCount: playlist.contentDetails.itemCount,
|
thumbnail: playlist.snippet.thumbnails.maxres || playlist.snippet.thumbnails.high,
|
||||||
votes: 0
|
songCount: playlist.contentDetails.itemCount,
|
||||||
};
|
votes: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.keys(details).length === 0) {
|
||||||
|
throw new Error("No valid playlists found");
|
||||||
|
}
|
||||||
|
|
||||||
return details;
|
return details;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching playlist details:', error);
|
console.error('Error fetching playlist details:', error);
|
||||||
@ -103,9 +192,36 @@ async function getPlaylistDetails() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = {
|
module.exports = {
|
||||||
fetchPlaylistSongs,
|
fetchPlaylistSongs,
|
||||||
getAvailableSongIds,
|
getAvailableSongIds,
|
||||||
PLAYLISTS,
|
PLAYLISTS,
|
||||||
getPlaylistDetails
|
getPlaylistDetails,
|
||||||
|
getRandomPlaylists,
|
||||||
|
validateAndCleanPlaylists,
|
||||||
|
validatePlaylist
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user