shivam413 commited on
Commit
8fa6702
·
verified ·
1 Parent(s): b6ff73a

Update pages/api/search.ts

Browse files
Files changed (1) hide show
  1. pages/api/search.ts +74 -31
pages/api/search.ts CHANGED
@@ -9,41 +9,84 @@ type YtResult = {
9
  thumbnails?: { url: string; width?: number; height?: number }[]
10
  }
11
 
12
- export default function handler(req: NextApiRequest, res: NextApiResponse) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  const q = (req.query.q || "").toString().trim()
14
  if (!q) return res.status(400).json({ error: "Missing q" })
15
 
16
- const limit = Math.max(1, Math.min(20, parseInt((req.query.limit || "10").toString(), 10) || 10))
17
  const term = `ytsearch${limit}:${q}`
18
 
19
- execFile(
20
- "yt-dlp",
21
- ["-J", term, "--no-warnings"],
22
- { maxBuffer: 10 * 1024 * 1024 },
23
- (err, stdout, stderr) => {
24
- if (err) {
25
- console.error("yt-dlp search failed", err, stderr)
26
- return res.status(500).json({ error: "search_failed" })
27
- }
28
- try {
29
- const data = JSON.parse(stdout)
30
- const entries = Array.isArray(data?.entries) ? data.entries : []
31
- const results: YtResult[] = entries
32
- .filter((e: any) => e?.id && e?.title)
33
- .map((e: any) => ({
34
- id: e.id,
35
- title: e.title,
36
- url: `https://www.youtube.com/watch?v=${e.id}`,
37
- duration: typeof e.duration === "number" ? e.duration : undefined,
38
- thumbnails: Array.isArray(e.thumbnails)
39
- ? e.thumbnails.map((t: any) => ({ url: t.url, width: t.width, height: t.height }))
40
- : undefined,
41
- }))
42
- res.json({ results })
43
- } catch (parseErr) {
44
- console.error("Failed to parse yt-dlp JSON", parseErr)
45
- res.status(500).json({ error: "parse_failed" })
46
- }
47
  }
48
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  }
 
9
  thumbnails?: { url: string; width?: number; height?: number }[]
10
  }
11
 
12
+ function run(cmd: string, args: string[]) {
13
+ return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
14
+ execFile(cmd, args, { maxBuffer: 16 * 1024 * 1024 }, (err, stdout, stderr) => {
15
+ if (err) return reject(Object.assign(err, { stdout, stderr }))
16
+ resolve({ stdout, stderr })
17
+ })
18
+ })
19
+ }
20
+
21
+ function mapLineJsonToResult(obj: any): YtResult | null {
22
+ const id = obj?.id
23
+ const title = obj?.title
24
+ if (!id || !title) return null
25
+ const url = obj?.webpage_url || `https://www.youtube.com/watch?v=${id}`
26
+ const duration = typeof obj?.duration === "number" ? obj.duration : undefined
27
+ const thumbs = Array.isArray(obj?.thumbnails)
28
+ ? obj.thumbnails.map((t: any) => ({ url: t.url, width: t.width, height: t.height }))
29
+ : obj?.thumbnail
30
+ ? [{ url: obj.thumbnail }]
31
+ : undefined
32
+ return { id, title, url, duration, thumbnails: thumbs }
33
+ }
34
+
35
+ export default async function handler(req: NextApiRequest, res: NextApiResponse) {
36
  const q = (req.query.q || "").toString().trim()
37
  if (!q) return res.status(400).json({ error: "Missing q" })
38
 
39
+ const limit = Math.max(1, Math.min(20, parseInt((req.query.limit || "8").toString(), 10) || 8))
40
  const term = `ytsearch${limit}:${q}`
41
 
42
+ try {
43
+ // Attempt A: Use IPv4 and per-entry JSON (-j). This avoids the “API page” failure mode.
44
+ const argsA = ["--force-ipv4", "-j", term, "--no-warnings"]
45
+ const { stdout } = await run("yt-dlp", argsA)
46
+
47
+ const resultsA: YtResult[] = stdout
48
+ .split("\n")
49
+ .map((line) => line.trim())
50
+ .filter((line) => line.startsWith("{") && line.endsWith("}"))
51
+ .map((line) => {
52
+ try {
53
+ return JSON.parse(line)
54
+ } catch {
55
+ return null
56
+ }
57
+ })
58
+ .filter(Boolean)
59
+ .map((obj) => mapLineJsonToResult(obj))
60
+ .filter((r): r is YtResult => !!r)
61
+
62
+ if (resultsA.length > 0) {
63
+ return res.json({ results: resultsA })
 
 
 
 
 
 
64
  }
65
+
66
+ // Fallback B: Print fields in a simple, parseable format (still forces IPv4).
67
+ const fmt = "%(id)s\t%(title)s\t%(webpage_url)s\t%(duration)s\t%(thumbnail)s"
68
+ const argsB = ["--force-ipv4", "--print", fmt, term, "--no-warnings"]
69
+ const outB = await run("yt-dlp", argsB)
70
+ const resultsB: YtResult[] = outB.stdout
71
+ .split("\n")
72
+ .map((l) => l.trim())
73
+ .filter(Boolean)
74
+ .map((line) => {
75
+ const [id, title, url, durationStr, thumb] = line.split("\t")
76
+ if (!id || !title) return null
77
+ const urlFinal = url || `https://www.youtube.com/watch?v=${id}`
78
+ const duration = Number.isFinite(Number(durationStr)) ? Number(durationStr) : undefined
79
+ const thumbnails = thumb ? [{ url: thumb }] : undefined
80
+ return { id, title, url: urlFinal, duration, thumbnails } as YtResult
81
+ })
82
+ .filter(Boolean) as YtResult[]
83
+
84
+ return res.json({ results: resultsB })
85
+ } catch (err: any) {
86
+ console.error("yt-dlp search failed", err)
87
+ return res.status(500).json({
88
+ error: "search_failed",
89
+ detail: err?.stderr || err?.message || "unknown_error",
90
+ })
91
+ }
92
  }