Back to Home
Gistify Snippet
Public
Pinterest Video & Image Downloader (PinDown)
Automated PinDown.io anti-bot token scraper to download original resolution Pinterest photos, GIFs, and videos.
#scraper#pinterest#downloader#media#javascript
0
Rawpindown.jsjavascript
165 lines| 1 | // pindown.js — bundle standalone. npm deps: axios, cheerio, node:url |
| 2 | |
| 3 | /** |
| 4 | * Scraper: pindown.js |
| 5 | * Source : kyioapi/pindown.js |
| 6 | * |
| 7 | * Input : Function parameters / CLI args (e.g. query: string, url: string, options: object) |
| 8 | * Output : Promise<Object | Array | Buffer> (JSON formatted scraping response / stream URL / media metadata) |
| 9 | * |
| 10 | * CLI Run: |
| 11 | * node kyioapi/pindown.js [functionName] [args...] |
| 12 | * node cli.js kyioapi/pindown.js [functionName] [args...] |
| 13 | */ |
| 14 | |
| 15 | import * as __ns_283 from 'axios' |
| 16 | const axios = __ns_283.default |
| 17 | import * as __ns_284 from 'cheerio' |
| 18 | const cheerio = __ns_284 |
| 19 | import * as __ns_285 from 'node:url' |
| 20 | const pathToFileURL = __ns_285.pathToFileURL |
| 21 | |
| 22 | |
| 23 | |
| 24 | |
| 25 | class PinDownAPI { |
| 26 | constructor() { |
| 27 | this.baseURL = "https://pindown.io"; |
| 28 | } |
| 29 | |
| 30 | async getDynamicToken() { |
| 31 | const response = await axios.get(`${this.baseURL}/en1`, { |
| 32 | headers: { |
| 33 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36' |
| 34 | } |
| 35 | }); |
| 36 | |
| 37 | const $ = cheerio.load(response.data); |
| 38 | |
| 39 | let tokenName = ''; |
| 40 | let tokenValue = ''; |
| 41 | |
| 42 | $('form input').each((i, el) => { |
| 43 | const name = $(el).attr('name'); |
| 44 | if (name && name !== 'url' && name !== 'lang') { |
| 45 | tokenName = name; |
| 46 | tokenValue = $(el).attr('value') || ''; |
| 47 | } |
| 48 | }); |
| 49 | |
| 50 | const rawCookies = response.headers['set-cookie'] || []; |
| 51 | const cookieString = rawCookies.map(c => c.split(';')[0]).join('; '); |
| 52 | |
| 53 | return { tokenName, tokenValue, cookieString }; |
| 54 | } |
| 55 | |
| 56 | async download(url, lang = "en") { |
| 57 | const { tokenName, tokenValue, cookieString } = await this.getDynamicToken(); |
| 58 | if (!tokenName) throw new Error("Gagal mengekstrak Anti-Bot Key dinamis dari halaman utama."); |
| 59 | |
| 60 | const formData = new URLSearchParams(); |
| 61 | formData.append("url", url); |
| 62 | formData.append(tokenName, tokenValue); |
| 63 | formData.append("lang", lang); |
| 64 | |
| 65 | const response = await axios({ |
| 66 | method: "POST", |
| 67 | url: `${this.baseURL}/action`, |
| 68 | headers: { |
| 69 | "accept": "*/*", |
| 70 | "accept-language": "en-US,en;q=0.9", |
| 71 | "cookie": cookieString, |
| 72 | "origin": this.baseURL, |
| 73 | "referer": `${this.baseURL}/en1`, |
| 74 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36", |
| 75 | "Content-Type": "application/x-www-form-urlencoded" |
| 76 | }, |
| 77 | data: formData.toString(), |
| 78 | }); |
| 79 | |
| 80 | return response.data; |
| 81 | } |
| 82 | |
| 83 | parseDownloadLinks(html) { |
| 84 | const $ = cheerio.load(html); |
| 85 | const links = []; |
| 86 | |
| 87 | $('table tbody tr').each((i, el) => { |
| 88 | const quality = $(el).find('td.video-quality').text().trim(); |
| 89 | const downloadUrl = $(el).find('a').attr('href'); |
| 90 | |
| 91 | if (downloadUrl && quality) { |
| 92 | links.push({ |
| 93 | quality: quality, |
| 94 | url: downloadUrl |
| 95 | }); |
| 96 | } |
| 97 | }); |
| 98 | |
| 99 | return links; |
| 100 | } |
| 101 | |
| 102 | async getDownloadLinks(url, lang = "en") { |
| 103 | try { |
| 104 | const result = await this.download(url, lang); |
| 105 | |
| 106 | if (result.success && result.html) { |
| 107 | const links = this.parseDownloadLinks(result.html); |
| 108 | return { |
| 109 | status: true, |
| 110 | result: links |
| 111 | }; |
| 112 | } |
| 113 | |
| 114 | return { |
| 115 | status: false, |
| 116 | creator: "ONLym-Api", |
| 117 | message: result.message || "Gagal mendapatkan data" |
| 118 | }; |
| 119 | } catch (error) { |
| 120 | return { |
| 121 | status: false, |
| 122 | creator: "ONLym-Api", |
| 123 | message: error.message |
| 124 | }; |
| 125 | } |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | const pindown = new PinDownAPI(); |
| 130 | export { PinDownAPI, pindown }; |
| 131 | export default pindown; |
| 132 | |
| 133 | // ───────────────────────────────────────────────────────────────────── |
| 134 | // Self-test: jalankan langsung dengan `node pindown.js [args...]` |
| 135 | // ───────────────────────────────────────────────────────────────────── |
| 136 | if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { |
| 137 | const __exports = { pindown: pindown } |
| 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 | let __args = process.argv.slice(2).map((a) => { try { return JSON.parse(a) } catch { return a } }) |
| 148 | if (__args.length === 0 && __search) __args.push('naruto') |
| 149 | if (__args.length === 0 && !__search && !__isClass && __fn.length > 0) { |
| 150 | console.log('Fungsi: ' + __names.join(', ')) |
| 151 | console.log('Contoh: node ' + "pindown.js" + ' ' + __name + ' <arg>') |
| 152 | } else { |
| 153 | ;(async () => { |
| 154 | try { |
| 155 | const r = __isClass ? new __fn(...__args) : await __fn(...__args) |
| 156 | console.log(JSON.stringify(r, null, 2)) |
| 157 | } catch (e) { |
| 158 | console.error('Error: ' + (e && e.message ? e.message : e)) |
| 159 | process.exitCode = 1 |
| 160 | } |
| 161 | })() |
| 162 | } |
| 163 | } |
| 164 | } |
| 165 | |
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.