Spaces:
Sleeping
Sleeping
Update server/server.js
Browse files- server/server.js +54 -50
server/server.js
CHANGED
|
@@ -91,11 +91,30 @@ function normalizeThumb(meta) {
|
|
| 91 |
meta?.image_url,
|
| 92 |
meta?.images?.[0],
|
| 93 |
meta?.images?.medium,
|
| 94 |
-
meta?.images?.cover
|
|
|
|
| 95 |
].filter(Boolean);
|
| 96 |
return candidates[0] || null;
|
| 97 |
}
|
| 98 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
// ---------- API endpoints (JioSaavn proxy) ----------
|
| 100 |
app.get('/api/result', async (req, res) => {
|
| 101 |
try {
|
|
@@ -154,13 +173,7 @@ app.get('/api/ytsearch', async (req, res) => {
|
|
| 154 |
if (!key) return res.status(501).json({ error: 'YouTube search unavailable: set YT_API_KEY env' });
|
| 155 |
const q = req.query.q || '';
|
| 156 |
const resp = await axios.get('https://www.googleapis.com/youtube/v3/search', {
|
| 157 |
-
params: {
|
| 158 |
-
key,
|
| 159 |
-
q,
|
| 160 |
-
part: 'snippet',
|
| 161 |
-
type: 'video',
|
| 162 |
-
maxResults: 10
|
| 163 |
-
},
|
| 164 |
timeout: 10000
|
| 165 |
});
|
| 166 |
const items = (resp.data.items || []).map(it => ({
|
|
@@ -211,7 +224,8 @@ io.on('connection', (socket) => {
|
|
| 211 |
if (!room.name && roomName) room.name = String(roomName).trim().slice(0, 60) || null;
|
| 212 |
|
| 213 |
const role = socket.id === room.hostId ? 'host' : 'member';
|
| 214 |
-
|
|
|
|
| 215 |
|
| 216 |
socket.join(roomId);
|
| 217 |
joinedRoom = roomId;
|
|
@@ -223,7 +237,7 @@ io.on('connection', (socket) => {
|
|
| 223 |
roomName: room.name || room.id
|
| 224 |
});
|
| 225 |
|
| 226 |
-
io.to(roomId).emit('system', { text: `System: ${
|
| 227 |
broadcastMembers(roomId);
|
| 228 |
});
|
| 229 |
|
|
@@ -248,15 +262,13 @@ io.on('connection', (socket) => {
|
|
| 248 |
socket.to(roomId).emit('chat_message', { name, text, at: Date.now() });
|
| 249 |
});
|
| 250 |
|
| 251 |
-
//
|
| 252 |
socket.on('song_request', ({ roomId, requester, query }) => {
|
| 253 |
const room = rooms.get(roomId);
|
| 254 |
if (!room) return;
|
| 255 |
const payload = { requester, query, at: Date.now(), requestId: `${Date.now()}_${Math.random().toString(36).slice(2)}` };
|
| 256 |
for (const [id, usr] of room.users.entries()) {
|
| 257 |
-
if (id === room.hostId || usr.role === 'cohost')
|
| 258 |
-
io.to(id).emit('song_request', payload);
|
| 259 |
-
}
|
| 260 |
}
|
| 261 |
io.to(roomId).emit('system', { text: `System: ${requester} requested /play ${query}` });
|
| 262 |
});
|
|
@@ -266,34 +278,31 @@ io.on('connection', (socket) => {
|
|
| 266 |
const room = rooms.get(roomId);
|
| 267 |
if (!room) return;
|
| 268 |
if (!requireHostOrCohost(room, socket.id)) return;
|
| 269 |
-
if (action === 'accept' && track) {
|
| 270 |
-
|
| 271 |
url: track.url,
|
| 272 |
title: track.title || track.url,
|
| 273 |
meta: track.meta || {},
|
| 274 |
kind: track.kind || 'audio',
|
| 275 |
thumb: track.thumb || normalizeThumb(track.meta || {})
|
| 276 |
};
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
});
|
| 291 |
-
io.to(roomId).emit('queue_update', { queue: room.queue });
|
| 292 |
-
io.to(roomId).emit('system', { text: `System: Queued ${track.title || track.url}` });
|
| 293 |
}
|
| 294 |
});
|
| 295 |
|
| 296 |
-
// Host controls
|
| 297 |
socket.on('set_track', ({ roomId, track }) => {
|
| 298 |
const room = rooms.get(roomId);
|
| 299 |
if (!room || !requireHostOrCohost(room, socket.id)) return;
|
|
@@ -333,7 +342,7 @@ io.on('connection', (socket) => {
|
|
| 333 |
io.to(roomId).emit('seek', { anchor: room.anchor, anchorAt: room.anchorAt, isPlaying: room.isPlaying });
|
| 334 |
});
|
| 335 |
|
| 336 |
-
// Ended -> auto next
|
| 337 |
socket.on('ended', ({ roomId }) => {
|
| 338 |
const room = rooms.get(roomId);
|
| 339 |
if (!room || !requireHostOrCohost(room, socket.id)) return;
|
|
@@ -347,7 +356,7 @@ io.on('connection', (socket) => {
|
|
| 347 |
io.to(roomId).emit('set_track', { track: room.track });
|
| 348 |
io.to(roomId).emit('pause', { anchor: 0, anchorAt: room.anchorAt });
|
| 349 |
io.to(roomId).emit('queue_update', { queue: room.queue });
|
| 350 |
-
io.to(roomId).emit('system', { text: `System: Now playing ${room.track.title
|
| 351 |
} else {
|
| 352 |
io.to(roomId).emit('system', { text: 'System: Queue ended' });
|
| 353 |
}
|
|
@@ -360,32 +369,26 @@ io.on('connection', (socket) => {
|
|
| 360 |
const actor = room.users.get(socket.id);
|
| 361 |
if (!actor || !(socket.id === room.hostId || actor.role === 'cohost')) return;
|
| 362 |
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
if (u.name && u.name.toLowerCase() === targetName.toLowerCase()) {
|
| 366 |
-
targetEntry = { id, u };
|
| 367 |
-
break;
|
| 368 |
-
}
|
| 369 |
-
}
|
| 370 |
-
if (!targetEntry) {
|
| 371 |
io.to(socket.id).emit('system', { text: `System: User @${targetName} not found` });
|
| 372 |
return;
|
| 373 |
}
|
| 374 |
|
| 375 |
if (cmd === 'kick') {
|
| 376 |
-
const { id } =
|
| 377 |
io.to(id).emit('system', { text: 'System: You were kicked by the host' });
|
| 378 |
io.sockets.sockets.get(id)?.leave(roomId);
|
| 379 |
room.users.delete(id);
|
| 380 |
-
io.to(roomId).emit('system', { text: `System: ${
|
| 381 |
broadcastMembers(roomId);
|
| 382 |
} else if (cmd === 'promote') {
|
| 383 |
-
|
| 384 |
-
io.to(roomId).emit('system', { text: `System: ${
|
| 385 |
broadcastMembers(roomId);
|
| 386 |
} else if (cmd === 'mute') {
|
| 387 |
-
|
| 388 |
-
io.to(roomId).emit('system', { text: `System: ${
|
| 389 |
broadcastMembers(roomId);
|
| 390 |
}
|
| 391 |
});
|
|
@@ -412,7 +415,8 @@ io.on('connection', (socket) => {
|
|
| 412 |
io.to(joinedRoom).emit('host_changed', { hostId: room.hostId });
|
| 413 |
}
|
| 414 |
if (leftUser) {
|
| 415 |
-
|
|
|
|
| 416 |
}
|
| 417 |
if (room.users.size === 0) {
|
| 418 |
rooms.delete(joinedRoom);
|
|
|
|
| 91 |
meta?.image_url,
|
| 92 |
meta?.images?.[0],
|
| 93 |
meta?.images?.medium,
|
| 94 |
+
meta?.images?.cover,
|
| 95 |
+
meta?.thumb
|
| 96 |
].filter(Boolean);
|
| 97 |
return candidates[0] || null;
|
| 98 |
}
|
| 99 |
|
| 100 |
+
function findUserByName(room, targetNameRaw) {
|
| 101 |
+
if (!targetNameRaw) return null;
|
| 102 |
+
const targetName = String(targetNameRaw).replace(/^@/, '').trim().toLowerCase();
|
| 103 |
+
// Prefer exact match, else startsWith, else includes
|
| 104 |
+
let candidate = null;
|
| 105 |
+
for (const [id, u] of room.users.entries()) {
|
| 106 |
+
const n = (u.name || '').toLowerCase();
|
| 107 |
+
if (n === targetName) return { id, u };
|
| 108 |
+
if (!candidate && n.startsWith(targetName)) candidate = { id, u };
|
| 109 |
+
}
|
| 110 |
+
if (candidate) return candidate;
|
| 111 |
+
for (const [id, u] of room.users.entries()) {
|
| 112 |
+
const n = (u.name || '').toLowerCase();
|
| 113 |
+
if (n.includes(targetName)) return { id, u };
|
| 114 |
+
}
|
| 115 |
+
return null;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
// ---------- API endpoints (JioSaavn proxy) ----------
|
| 119 |
app.get('/api/result', async (req, res) => {
|
| 120 |
try {
|
|
|
|
| 173 |
if (!key) return res.status(501).json({ error: 'YouTube search unavailable: set YT_API_KEY env' });
|
| 174 |
const q = req.query.q || '';
|
| 175 |
const resp = await axios.get('https://www.googleapis.com/youtube/v3/search', {
|
| 176 |
+
params: { key, q, part: 'snippet', type: 'video', maxResults: 12 },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
timeout: 10000
|
| 178 |
});
|
| 179 |
const items = (resp.data.items || []).map(it => ({
|
|
|
|
| 224 |
if (!room.name && roomName) room.name = String(roomName).trim().slice(0, 60) || null;
|
| 225 |
|
| 226 |
const role = socket.id === room.hostId ? 'host' : 'member';
|
| 227 |
+
const cleanName = String(name || 'Guest').slice(0, 40);
|
| 228 |
+
room.users.set(socket.id, { name: cleanName, role });
|
| 229 |
|
| 230 |
socket.join(roomId);
|
| 231 |
joinedRoom = roomId;
|
|
|
|
| 237 |
roomName: room.name || room.id
|
| 238 |
});
|
| 239 |
|
| 240 |
+
io.to(roomId).emit('system', { text: `System: ${cleanName} has joined the chat` });
|
| 241 |
broadcastMembers(roomId);
|
| 242 |
});
|
| 243 |
|
|
|
|
| 262 |
socket.to(roomId).emit('chat_message', { name, text, at: Date.now() });
|
| 263 |
});
|
| 264 |
|
| 265 |
+
// /play request
|
| 266 |
socket.on('song_request', ({ roomId, requester, query }) => {
|
| 267 |
const room = rooms.get(roomId);
|
| 268 |
if (!room) return;
|
| 269 |
const payload = { requester, query, at: Date.now(), requestId: `${Date.now()}_${Math.random().toString(36).slice(2)}` };
|
| 270 |
for (const [id, usr] of room.users.entries()) {
|
| 271 |
+
if (id === room.hostId || usr.role === 'cohost') io.to(id).emit('song_request', payload);
|
|
|
|
|
|
|
| 272 |
}
|
| 273 |
io.to(roomId).emit('system', { text: `System: ${requester} requested /play ${query}` });
|
| 274 |
});
|
|
|
|
| 278 |
const room = rooms.get(roomId);
|
| 279 |
if (!room) return;
|
| 280 |
if (!requireHostOrCohost(room, socket.id)) return;
|
| 281 |
+
if ((action === 'accept' || action === 'queue') && track) {
|
| 282 |
+
const t = {
|
| 283 |
url: track.url,
|
| 284 |
title: track.title || track.url,
|
| 285 |
meta: track.meta || {},
|
| 286 |
kind: track.kind || 'audio',
|
| 287 |
thumb: track.thumb || normalizeThumb(track.meta || {})
|
| 288 |
};
|
| 289 |
+
if (action === 'accept') {
|
| 290 |
+
room.track = t;
|
| 291 |
+
room.isPlaying = false;
|
| 292 |
+
room.anchor = 0;
|
| 293 |
+
room.anchorAt = Date.now();
|
| 294 |
+
io.to(roomId).emit('set_track', { track: room.track });
|
| 295 |
+
io.to(roomId).emit('pause', { anchor: 0, anchorAt: room.anchorAt });
|
| 296 |
+
io.to(roomId).emit('system', { text: `System: Now playing ${room.track.title}` });
|
| 297 |
+
} else {
|
| 298 |
+
room.queue.push(t);
|
| 299 |
+
io.to(roomId).emit('queue_update', { queue: room.queue });
|
| 300 |
+
io.to(roomId).emit('system', { text: `System: Queued ${t.title}` });
|
| 301 |
+
}
|
|
|
|
|
|
|
|
|
|
| 302 |
}
|
| 303 |
});
|
| 304 |
|
| 305 |
+
// Host/cohost controls
|
| 306 |
socket.on('set_track', ({ roomId, track }) => {
|
| 307 |
const room = rooms.get(roomId);
|
| 308 |
if (!room || !requireHostOrCohost(room, socket.id)) return;
|
|
|
|
| 342 |
io.to(roomId).emit('seek', { anchor: room.anchor, anchorAt: room.anchorAt, isPlaying: room.isPlaying });
|
| 343 |
});
|
| 344 |
|
| 345 |
+
// Ended -> auto next
|
| 346 |
socket.on('ended', ({ roomId }) => {
|
| 347 |
const room = rooms.get(roomId);
|
| 348 |
if (!room || !requireHostOrCohost(room, socket.id)) return;
|
|
|
|
| 356 |
io.to(roomId).emit('set_track', { track: room.track });
|
| 357 |
io.to(roomId).emit('pause', { anchor: 0, anchorAt: room.anchorAt });
|
| 358 |
io.to(roomId).emit('queue_update', { queue: room.queue });
|
| 359 |
+
io.to(roomId).emit('system', { text: `System: Now playing ${room.track.title}` });
|
| 360 |
} else {
|
| 361 |
io.to(roomId).emit('system', { text: 'System: Queue ended' });
|
| 362 |
}
|
|
|
|
| 369 |
const actor = room.users.get(socket.id);
|
| 370 |
if (!actor || !(socket.id === room.hostId || actor.role === 'cohost')) return;
|
| 371 |
|
| 372 |
+
const found = findUserByName(room, targetName);
|
| 373 |
+
if (!found) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
io.to(socket.id).emit('system', { text: `System: User @${targetName} not found` });
|
| 375 |
return;
|
| 376 |
}
|
| 377 |
|
| 378 |
if (cmd === 'kick') {
|
| 379 |
+
const { id } = found;
|
| 380 |
io.to(id).emit('system', { text: 'System: You were kicked by the host' });
|
| 381 |
io.sockets.sockets.get(id)?.leave(roomId);
|
| 382 |
room.users.delete(id);
|
| 383 |
+
io.to(roomId).emit('system', { text: `System: ${found.u.name} was kicked` });
|
| 384 |
broadcastMembers(roomId);
|
| 385 |
} else if (cmd === 'promote') {
|
| 386 |
+
found.u.role = 'cohost';
|
| 387 |
+
io.to(roomId).emit('system', { text: `System: ${found.u.name} was promoted to co-host` });
|
| 388 |
broadcastMembers(roomId);
|
| 389 |
} else if (cmd === 'mute') {
|
| 390 |
+
found.u.muted = true;
|
| 391 |
+
io.to(roomId).emit('system', { text: `System: ${found.u.name} was muted` });
|
| 392 |
broadcastMembers(roomId);
|
| 393 |
}
|
| 394 |
});
|
|
|
|
| 415 |
io.to(joinedRoom).emit('host_changed', { hostId: room.hostId });
|
| 416 |
}
|
| 417 |
if (leftUser) {
|
| 418 |
+
const nm = leftUser.name || 'User';
|
| 419 |
+
io.to(joinedRoom).emit('system', { text: `System: ${nm} has left the chat` });
|
| 420 |
}
|
| 421 |
if (room.users.size === 0) {
|
| 422 |
rooms.delete(joinedRoom);
|