akborana4 commited on
Commit
ad51789
·
verified ·
1 Parent(s): bf98e79

Update server/server.js

Browse files
Files changed (1) hide show
  1. server/server.js +73 -27
server/server.js CHANGED
@@ -5,6 +5,7 @@ import { Server as SocketIOServer } from 'socket.io';
5
  import path from 'path';
6
  import { fileURLToPath } from 'url';
7
  import axios from 'axios';
 
8
  import {
9
  searchUniversal,
10
  getSong,
@@ -84,15 +85,14 @@ function requireHostOrCohost(room, socketId) {
84
 
85
  function normalizeThumb(meta) {
86
  const candidates = [
 
87
  meta?.image,
88
  meta?.thumbnail,
89
  meta?.song_image,
90
  meta?.album_image,
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
  }
@@ -100,7 +100,6 @@ function normalizeThumb(meta) {
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();
@@ -115,7 +114,7 @@ function findUserByName(room, targetNameRaw) {
115
  return null;
116
  }
117
 
118
- // ---------- API endpoints (JioSaavn proxy) ----------
119
  app.get('/api/result', async (req, res) => {
120
  try {
121
  const q = req.query.q || '';
@@ -125,48 +124,36 @@ app.get('/api/result', async (req, res) => {
125
  res.status(500).json({ error: e.message });
126
  }
127
  });
128
-
129
  app.get('/api/song', async (req, res) => {
130
  try {
131
  const q = req.query.q || '';
132
  const data = await getSong(q);
133
  res.json(data);
134
- } catch (e) {
135
- res.status(500).json({ error: e.message });
136
- }
137
  });
138
-
139
  app.get('/api/album', async (req, res) => {
140
  try {
141
  const q = req.query.q || '';
142
  const data = await getAlbum(q);
143
  res.json(data);
144
- } catch (e) {
145
- res.status(500).json({ error: e.message });
146
- }
147
  });
148
-
149
  app.get('/api/playlist', async (req, res) => {
150
  try {
151
  const q = req.query.q || '';
152
  const data = await getPlaylist(q);
153
  res.json(data);
154
- } catch (e) {
155
- res.status(500).json({ error: e.message });
156
- }
157
  });
158
-
159
  app.get('/api/lyrics', async (req, res) => {
160
  try {
161
  const q = req.query.q || '';
162
  const data = await getLyrics(q);
163
  res.json(data);
164
- } catch (e) {
165
- res.status(500).json({ error: e.message });
166
- }
167
  });
168
 
169
- // ---------- YouTube search (requires YT_API_KEY env) ----------
170
  app.get('/api/ytsearch', async (req, res) => {
171
  try {
172
  const key = process.env.YT_API_KEY;
@@ -188,6 +175,67 @@ app.get('/api/ytsearch', async (req, res) => {
188
  }
189
  });
190
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  // Active rooms for lobby
192
  app.get('/api/rooms', (_req, res) => {
193
  const data = [...rooms.values()]
@@ -237,6 +285,7 @@ io.on('connection', (socket) => {
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
  });
@@ -418,11 +467,8 @@ io.on('connection', (socket) => {
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);
423
- } else {
424
- broadcastMembers(joinedRoom);
425
- }
426
  });
427
  });
428
 
 
5
  import path from 'path';
6
  import { fileURLToPath } from 'url';
7
  import axios from 'axios';
8
+ import { spawn } from 'child_process';
9
  import {
10
  searchUniversal,
11
  getSong,
 
85
 
86
  function normalizeThumb(meta) {
87
  const candidates = [
88
+ meta?.thumb,
89
  meta?.image,
90
  meta?.thumbnail,
91
  meta?.song_image,
92
  meta?.album_image,
93
  meta?.image_url,
 
 
94
  meta?.images?.cover,
95
+ meta?.images?.[0]
96
  ].filter(Boolean);
97
  return candidates[0] || null;
98
  }
 
100
  function findUserByName(room, targetNameRaw) {
101
  if (!targetNameRaw) return null;
102
  const targetName = String(targetNameRaw).replace(/^@/, '').trim().toLowerCase();
 
103
  let candidate = null;
104
  for (const [id, u] of room.users.entries()) {
105
  const n = (u.name || '').toLowerCase();
 
114
  return null;
115
  }
116
 
117
+ // ---------- JioSaavn proxies ----------
118
  app.get('/api/result', async (req, res) => {
119
  try {
120
  const q = req.query.q || '';
 
124
  res.status(500).json({ error: e.message });
125
  }
126
  });
 
127
  app.get('/api/song', async (req, res) => {
128
  try {
129
  const q = req.query.q || '';
130
  const data = await getSong(q);
131
  res.json(data);
132
+ } catch (e) { res.status(500).json({ error: e.message }); }
 
 
133
  });
 
134
  app.get('/api/album', async (req, res) => {
135
  try {
136
  const q = req.query.q || '';
137
  const data = await getAlbum(q);
138
  res.json(data);
139
+ } catch (e) { res.status(500).json({ error: e.message }); }
 
 
140
  });
 
141
  app.get('/api/playlist', async (req, res) => {
142
  try {
143
  const q = req.query.q || '';
144
  const data = await getPlaylist(q);
145
  res.json(data);
146
+ } catch (e) { res.status(500).json({ error: e.message }); }
 
 
147
  });
 
148
  app.get('/api/lyrics', async (req, res) => {
149
  try {
150
  const q = req.query.q || '';
151
  const data = await getLyrics(q);
152
  res.json(data);
153
+ } catch (e) { res.status(500).json({ error: e.message }); }
 
 
154
  });
155
 
156
+ // ---------- YouTube search (YT_API_KEY) ----------
157
  app.get('/api/ytsearch', async (req, res) => {
158
  try {
159
  const key = process.env.YT_API_KEY;
 
175
  }
176
  });
177
 
178
+ // ---------- YouTube resolve via yt-dlp (stream URL) ----------
179
+ function runYtDlp(url) {
180
+ return new Promise((resolve, reject) => {
181
+ const args = ['-J', '--no-warnings', '--no-call-home'];
182
+ const cookies = process.env.YT_COOKIES;
183
+ if (cookies) { args.push('--cookies', cookies); }
184
+ args.push(url);
185
+
186
+ const proc = spawn('yt-dlp', args, { stdio: ['ignore', 'pipe', 'pipe'] });
187
+ let out = '';
188
+ let err = '';
189
+ proc.stdout.on('data', (d) => { out += d.toString('utf8'); });
190
+ proc.stderr.on('data', (d) => { err += d.toString('utf8'); });
191
+ proc.on('close', (code) => {
192
+ if (code !== 0) return reject(new Error(err || `yt-dlp failed with code ${code}`));
193
+ try {
194
+ const json = JSON.parse(out);
195
+ resolve(json);
196
+ } catch (e) { reject(e); }
197
+ });
198
+ });
199
+ }
200
+
201
+ // Choose a decent progressive MP4 or best video/audio URL
202
+ function selectBestFormat(info) {
203
+ const entries = info.entries || null;
204
+ const base = entries ? entries[0] : info;
205
+ const formats = base.formats || [];
206
+ // Prefer mp4 progressive (has both audio+video)
207
+ let best = formats
208
+ .filter(f => f.ext === 'mp4' && f.vcodec && f.acodec && f.vcodec !== 'none' && f.acodec !== 'none')
209
+ .sort((a, b) => (b.height || 0) - (a.height || 0))[0];
210
+ if (!best) {
211
+ // Fallback: bestvideo+bestaudio (we pick bestvideo with audio if available)
212
+ best = formats
213
+ .filter(f => f.url && (f.acodec !== 'none' || f.vcodec !== 'none'))
214
+ .sort((a, b) => (b.tbr || 0) - (a.tbr || 0))[0];
215
+ }
216
+ return {
217
+ url: best?.url,
218
+ title: base.title || info.title,
219
+ thumbnail: base.thumbnail || info.thumbnail,
220
+ duration: base.duration || info.duration,
221
+ kind: 'video'
222
+ };
223
+ }
224
+
225
+ app.get('/api/yt/source', async (req, res) => {
226
+ try {
227
+ const raw = (req.query.url || '').trim();
228
+ if (!raw) return res.status(400).json({ error: 'Missing url' });
229
+ const url = /^[a-zA-Z0-9_-]{11}$/.test(raw) ? `https://www.youtube.com/watch?v=${raw}` : raw;
230
+ const info = await runYtDlp(url);
231
+ const best = selectBestFormat(info);
232
+ if (!best.url) return res.status(500).json({ error: 'No playable format found' });
233
+ res.json(best);
234
+ } catch (e) {
235
+ res.status(500).json({ error: e.message });
236
+ }
237
+ });
238
+
239
  // Active rooms for lobby
240
  app.get('/api/rooms', (_req, res) => {
241
  const data = [...rooms.values()]
 
285
  roomName: room.name || room.id
286
  });
287
 
288
+ // Broadcast one clean system message to everyone
289
  io.to(roomId).emit('system', { text: `System: ${cleanName} has joined the chat` });
290
  broadcastMembers(roomId);
291
  });
 
467
  const nm = leftUser.name || 'User';
468
  io.to(joinedRoom).emit('system', { text: `System: ${nm} has left the chat` });
469
  }
470
+ if (room.users.size === 0) rooms.delete(joinedRoom);
471
+ else broadcastMembers(joinedRoom);
 
 
 
472
  });
473
  });
474