Back to Home
Gistify Snippet
kyiov
Sep 3ยท1 file(s)
Public

Scraper spotyloader

Scraper spotyloader

#github-gist#javascript#gist:f3c35e733e9a575da954a945f1611438#gistsync:2026-09-02T12:40:42Z
1
Raw
spotyloader.jsjavascript
176 lines
1
2
3import * as __ns_794 from 'node:url'
4const pathToFileURL = __ns_794.pathToFileURL
5
6const BASE = 'https://spotyloader.com'
7
8const headers = {
9 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
10 'accept': '*/*',
11 'content-type': 'application/json',
12 'origin': BASE,
13 'referer': BASE + '/',
14}
15
16function detectType(url) {
17 if (!url || typeof url !== 'string') return null
18 const u = url.trim()
19 if (/^spotify:(track|playlist|album|artist):[A-Za-z0-9]+$/i.test(u)) {
20 const m = u.match(/^spotify:(track|playlist|album|artist):/i)
21 return m[1].toLowerCase()
22 }
23 try {
24 const parsed = new URL(u)
25 const host = parsed.hostname.toLowerCase()
26 if (host !== 'open.spotify.com' && host !== 'spotify.com' && !host.endsWith('.spotify.com')) return null
27 const m = parsed.pathname.match(/^\/(?:intl-[a-z-]+\/)?(track|playlist|album|artist)\/([A-Za-z0-9]+)/i)
28 return m ? m[1].toLowerCase() : null
29 } catch {
30 return null
31 }
32}
33
34function fmtDuration(ms) {
35 const s = Math.round(ms / 1000)
36 const m = Math.floor(s / 60)
37 return m + ':' + String(s % 60).padStart(2, '0')
38}
39
40function checkError(status, data) {
41 if (data?.error) throw new Error(data.error)
42 if (data?.message && data.success === false) throw new Error(data.message)
43 if (status >= 400) throw new Error(`HTTP ${status}`)
44}
45
46async function apiGet(path) {
47 const r = await fetch(BASE + path, { headers, cache: 'no-store' })
48 const data = await r.json()
49 checkError(r.status, data)
50 return data
51}
52
53async function apiPost(path, body) {
54 const r = await fetch(BASE + path, {
55 method: 'POST',
56 headers,
57 body: JSON.stringify(body),
58 cache: 'no-store',
59 })
60 const data = await r.json()
61 checkError(r.status, data)
62 return data
63}
64
65async function getInfo(url) {
66 const type = detectType(url)
67 if (!type) throw new Error('Invalid Spotify URL')
68 const encoded = encodeURIComponent(url.trim())
69 if (type === 'track') {
70 return { type, data: (await apiGet(`/api/spotify/info?url=${encoded}`)).post || (await apiGet(`/api/spotify/info?url=${encoded}`)) }
71 }
72 if (type === 'artist') {
73 return { type, data: (await apiGet(`/api/spotify/artist?url=${encoded}`)).post }
74 }
75 if (type === 'playlist' || type === 'album') {
76 return { type, data: (await apiPost(`/api/spotify/${type}`, { url: url.trim() })).post }
77 }
78 throw new Error('Unsupported type: ' + type)
79}
80
81async function convertTrack(url, format = 'mp3') {
82 const r = await apiPost('/api/spotify/track', { url: url.trim(), format })
83 if (!r.jobId) throw new Error('Failed to start conversion')
84 return r.jobId
85}
86
87async function pollStatus(jobId, maxAttempts = 30, interval = 2000) {
88 for (let i = 0; i < maxAttempts; i++) {
89 await new Promise(r => setTimeout(r, interval))
90 const r = await apiGet(`/api/spotify/track/status/${jobId}`)
91 if (r.status === 'ready' && r.downloadLink) return r
92 if (r.status === 'error') throw new Error(r.error || 'Conversion failed')
93 if (r.status !== 'pending' && r.status !== 'processing') throw new Error('Unknown status: ' + r.status)
94 }
95 throw new Error('Timeout: conversion took too long')
96}
97
98async function downloadTrack(url, format = 'mp3') {
99 const jobId = await convertTrack(url, format)
100 const result = await pollStatus(jobId)
101 return {
102 status: true,
103 name: result.post?.name || 'unknown',
104 artist: result.post?.artist || 'unknown',
105 album: result.post?.album || '',
106 format: result.post?.format || format,
107 download_url: result.downloadLink,
108 duration: result.post?.duration_ms ? fmtDuration(result.post.duration_ms) : '',
109 image: result.post?.image || '',
110 }
111}
112
113async function searchTracks(query, limit = 10) {
114 const encoded = encodeURIComponent(query)
115 const data = await apiGet(`/api/spotify/search?q=${encoded}&limit=${limit}`)
116 return (data.items || []).map(t => ({
117 name: t.name,
118 artist: t.artist,
119 url: t.url,
120 duration: fmtDuration(t.duration_ms),
121 image: t.image || '',
122 source: t.source,
123 }))
124}
125
126const __dflt_26 = {
127 detectType,
128 fmtDuration,
129 getInfo,
130 convertTrack,
131 pollStatus,
132 downloadTrack,
133 searchTracks
134}
135
136if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
137 const __exports = { getInfo: getInfo, convertTrack: convertTrack, pollStatus: pollStatus, downloadTrack: downloadTrack, searchTracks: searchTracks, detectType: detectType, fmtDuration: fmtDuration, default: __dflt_26 }
138 const __fns = Object.fromEntries(Object.entries(__exports).filter(([, v]) => typeof v === 'function'))
139 const __names = Object.keys(__fns)
140 if (__names.length === 0) {
141 console.log('Tidak ada fungsi yang bisa di-demo. Import modul ini dari kode lain.')
142 } else {
143 const __search = __names.find((n) => /search|cari/i.test(n))
144 const __name = __search || __names[0]
145 const __fn = __fns[__name]
146 const __isClass = /^class\s/.test(Function.prototype.toString.call(__fn))
147 const __defaultArgs = (() => {
148 const n = __name.toLowerCase()
149 if (/emoji/.test(n)) return [{ emoji: '๐Ÿ˜€๐Ÿ˜ƒ' }]
150 if (/textpro|generate.*text|effect/.test(n)) return ['neon', 'Hello']
151 if (/image|wallpaper|search|query|anime|movie|manga|news|lyrics|wiki|quote|stalk|detail/.test(n)) return ['naruto']
152 if (/download|youtube|video|audio|music|spotify|tiktok|instagram|facebook|twitter|reels|terabox|media/.test(n)) return ['https://www.youtube.com/watch?v=dQw4w9WgXcQ']
153 if (/dork/.test(n)) return ['example.com', 'documents']
154 if (/tts|speech|prompt|chat|ask|translate/.test(n)) return ['Hello world']
155 if (/parse|format|normalize|list|features/.test(n)) return []
156 return ['test']
157 })()
158 let __args = __defaultArgs
159 if (process.argv.length > 2) __args = process.argv.slice(2).map((a) => { try { return JSON.parse(a) } catch { return a } })
160 if (__args.length === 0 && !__search && !__isClass && __fn.length > 0) {
161 console.log('Fungsi: ' + __names.join(', '))
162 console.log('Contoh: node ' + "spotyloader.js" + ' ' + __name + ' <arg>')
163 } else {
164 ;(async () => {
165 try {
166 const r = __isClass ? new __fn(...__args) : await __fn(...__args)
167 console.log(JSON.stringify(r, null, 2))
168 } catch (e) {
169 console.error('Error: ' + (e && e.message ? e.message : e))
170 process.exitCode = 1
171 }
172 })()
173 }
174 }
175}
176
Discussion & Comments0

No comments yet โ€” be the first to leave feedback or start a code discussion.

Sign in with your GitHub account to write comments and join the discussion.