Back to Home
Gistify Snippet
Public
Scraper instagram
Scraper instagram
#github-gist#javascript#gist:dc79900c0c757316c05e05b5dbf98a01#gistsync:2026-09-02T12:40:35Z
0
Rawinstagram.jsjavascript
498 lines| 1 | |
| 2 | |
| 3 | import * as __ns_637 from 'axios' |
| 4 | const axios = __ns_637.default |
| 5 | import * as __ns_638 from 'cheerio' |
| 6 | const cheerio = __ns_638 |
| 7 | import * as __ns_639 from 'node:fs' |
| 8 | const fs = __ns_639.default |
| 9 | import * as __ns_640 from 'node:path' |
| 10 | const path = __ns_640.default |
| 11 | import * as __ns_641 from 'node:url' |
| 12 | const pathToFileURL = __ns_641.pathToFileURL |
| 13 | const fileURLToPath = __ns_641.fileURLToPath |
| 14 | |
| 15 | const BASE_URL = 'https://www.save-free.com' |
| 16 | const AJAX_URL = `${BASE_URL}/wp-admin/admin-ajax.php` |
| 17 | |
| 18 | async function scrapeSaveFreeProfile(usernameOrUrl, options = {}) { |
| 19 | const { cfToken = '', lang = 'en' } = options |
| 20 | |
| 21 | const cleanInput = String(usernameOrUrl || '') |
| 22 | .trim() |
| 23 | .replace(/^https?:\/\/(www\.)?instagram\.com\//i, '') |
| 24 | .replace(/\/.*$/, '') |
| 25 | .replace(/^@/, '') |
| 26 | |
| 27 | if (!cleanInput) { |
| 28 | throw new Error('Username atau URL Instagram tidak valid.') |
| 29 | } |
| 30 | |
| 31 | const formData = new URLSearchParams() |
| 32 | formData.append('action', 'save_profile') |
| 33 | formData.append('url', cleanInput) |
| 34 | formData.append('lang', lang) |
| 35 | if (cfToken) { |
| 36 | formData.append('cf_token', cfToken) |
| 37 | formData.append('cf-turnstile-response', cfToken) |
| 38 | } |
| 39 | |
| 40 | const headers = { |
| 41 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', |
| 42 | 'Referer': `${BASE_URL}/en/profile-downloader-2/`, |
| 43 | 'Origin': BASE_URL, |
| 44 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', |
| 45 | 'X-Requested-With': 'XMLHttpRequest', |
| 46 | 'Accept': '*/*' |
| 47 | } |
| 48 | |
| 49 | const response = await axios.post(AJAX_URL, formData.toString(), { |
| 50 | headers, |
| 51 | timeout: 30000, |
| 52 | validateStatus: () => true |
| 53 | }) |
| 54 | |
| 55 | if (response.status === 403 || (typeof response.data === 'string' && response.data.includes('Just a moment...'))) { |
| 56 | return { |
| 57 | status: false, |
| 58 | code: 403, |
| 59 | error: 'Cloudflare Turnstile WAF Protection Aktif. Membutuhkan CF token atau solver.', |
| 60 | cfRequired: true, |
| 61 | endpoint: AJAX_URL, |
| 62 | payload: Object.fromEntries(formData.entries()) |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | const rawData = response.data |
| 67 | const $ = cheerio.load(typeof rawData === 'string' ? rawData : (rawData.html || rawData.data || '')) |
| 68 | |
| 69 | const profilePic = $('img.ig_img, .profile-pic img, .avatar img, .result-card img').attr('src') || $('a.btn-download').attr('href') || null |
| 70 | const hdProfilePic = $('a.download_image_HD, a.btn-hd, a[download]').attr('href') || profilePic |
| 71 | const name = $('.profile-name, .user-name, h3').first().text().trim() || cleanInput |
| 72 | const username = $('.profile-username, .user-username').first().text().trim() || cleanInput |
| 73 | const bio = $('.profile-bio, .user-bio').first().text().trim() || '-' |
| 74 | const posts = $('.profile-posts, .posts-count').first().text().trim() || '0' |
| 75 | const followers = $('.profile-followers, .followers-count').first().text().trim() || '0' |
| 76 | const following = $('.profile-following, .following-count').first().text().trim() || '0' |
| 77 | |
| 78 | return { |
| 79 | status: true, |
| 80 | result: { |
| 81 | username, |
| 82 | name, |
| 83 | bio, |
| 84 | stats: { |
| 85 | posts, |
| 86 | followers, |
| 87 | following |
| 88 | }, |
| 89 | avatar: profilePic, |
| 90 | avatarHd: hdProfilePic, |
| 91 | rawHtml: typeof rawData === 'string' ? rawData.slice(0, 1000) : rawData |
| 92 | } |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | async function scrapeSaveFreeStories(usernameOrUrl, options = {}) { |
| 97 | const { cfToken = '', lang = 'en' } = options |
| 98 | |
| 99 | const cleanInput = String(usernameOrUrl || '') |
| 100 | .trim() |
| 101 | .replace(/^https?:\/\/(www\.)?instagram\.com\/stories\//i, '') |
| 102 | .replace(/^https?:\/\/(www\.)?instagram\.com\//i, '') |
| 103 | .replace(/\/.*$/, '') |
| 104 | .replace(/^@/, '') |
| 105 | |
| 106 | if (!cleanInput) { |
| 107 | throw new Error('Username atau link Instagram Story tidak valid.') |
| 108 | } |
| 109 | |
| 110 | const formData = new URLSearchParams() |
| 111 | formData.append('action', 'save_story') |
| 112 | formData.append('url', cleanInput) |
| 113 | formData.append('lang', lang) |
| 114 | if (cfToken) { |
| 115 | formData.append('cf_token', cfToken) |
| 116 | formData.append('cf-turnstile-response', cfToken) |
| 117 | } |
| 118 | |
| 119 | const headers = { |
| 120 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', |
| 121 | 'Referer': `${BASE_URL}/en/stories-downloader/`, |
| 122 | 'Origin': BASE_URL, |
| 123 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', |
| 124 | 'X-Requested-With': 'XMLHttpRequest', |
| 125 | 'Accept': '*/*' |
| 126 | } |
| 127 | |
| 128 | const response = await axios.post(AJAX_URL, formData.toString(), { |
| 129 | headers, |
| 130 | timeout: 30000, |
| 131 | validateStatus: () => true |
| 132 | }) |
| 133 | |
| 134 | if (response.status === 403 || (typeof response.data === 'string' && response.data.includes('Just a moment...'))) { |
| 135 | return { |
| 136 | status: false, |
| 137 | code: 403, |
| 138 | error: 'Cloudflare Turnstile WAF Protection Aktif.', |
| 139 | cfRequired: true, |
| 140 | endpoint: AJAX_URL, |
| 141 | payload: Object.fromEntries(formData.entries()) |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | const rawData = response.data |
| 146 | const $ = cheerio.load(typeof rawData === 'string' ? rawData : (rawData.html || rawData.data || '')) |
| 147 | |
| 148 | const stories = [] |
| 149 | $('.media-box, .result-card, .story-item, .card').each((_, el) => { |
| 150 | const card = $(el) |
| 151 | const videoUrl = card.find('video[src], video source[src]').attr('src') || card.find('a[href*=".mp4"]').attr('href') |
| 152 | const imgUrl = card.find('img.ig_img, img[src]').attr('src') || card.find('a[href*=".jpg"], a[href*=".png"]').attr('href') |
| 153 | const dlLink = card.find('a.btn-download, a[download]').attr('href') || videoUrl || imgUrl |
| 154 | |
| 155 | if (dlLink) { |
| 156 | stories.push({ |
| 157 | type: videoUrl || dlLink.includes('.mp4') ? 'video' : 'image', |
| 158 | url: dlLink, |
| 159 | thumbnail: imgUrl || null |
| 160 | }) |
| 161 | } |
| 162 | }) |
| 163 | |
| 164 | return { |
| 165 | status: stories.length > 0, |
| 166 | username: cleanInput, |
| 167 | total: stories.length, |
| 168 | stories |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | const __dflt_16 = { |
| 173 | scrapeSaveFreeProfile, |
| 174 | scrapeSaveFreeStories |
| 175 | } |
| 176 | |
| 177 | const GOOGLEBOT_UA = 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' |
| 178 | const FACEBOOK_BOT_UA = 'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)' |
| 179 | const DESKTOP_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36' |
| 180 | const DEFAULT_HEADERS = { |
| 181 | 'User-Agent': GOOGLEBOT_UA, |
| 182 | Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', |
| 183 | 'Accept-Language': 'en-US,en;q=0.9,id;q=0.8', |
| 184 | } |
| 185 | |
| 186 | function formatNumber(num) { |
| 187 | if (num === null || num === undefined || Number.isNaN(Number(num))) return '0' |
| 188 | const n = Number(num) |
| 189 | if (n >= 1e9) return `${(n / 1e9).toFixed(1).replace(/\.0$/, '')}B` |
| 190 | if (n >= 1e6) return `${(n / 1e6).toFixed(1).replace(/\.0$/, '')}M` |
| 191 | if (n >= 1e3) return `${(n / 1e3).toFixed(1).replace(/\.0$/, '')}K` |
| 192 | return n.toLocaleString('id-ID') |
| 193 | } |
| 194 | |
| 195 | function deepFind(root, predicate, maxResults = 10) { |
| 196 | const results = [] |
| 197 | const visited = new Set() |
| 198 | function recurse(node, key, parent) { |
| 199 | if (!node || typeof node !== 'object' || visited.has(node) || results.length >= maxResults) return |
| 200 | visited.add(node) |
| 201 | try { |
| 202 | if (predicate(node, key, parent)) results.push(node) |
| 203 | } catch {} |
| 204 | if (Array.isArray(node)) node.forEach((item, index) => recurse(item, index, node)) |
| 205 | else Object.keys(node).forEach((item) => recurse(node[item], item, node)) |
| 206 | } |
| 207 | recurse(root, null, null) |
| 208 | return results |
| 209 | } |
| 210 | |
| 211 | function normalizeInstagramUrl(rawUrl) { |
| 212 | if (!rawUrl || typeof rawUrl !== 'string') throw new Error('URL Instagram wajib disertakan!') |
| 213 | let cleanUrl = rawUrl.trim() |
| 214 | const matchUrl = cleanUrl.match(/https?:\/\/[^\s]+/) |
| 215 | if (matchUrl) cleanUrl = matchUrl[0] |
| 216 | return `${cleanUrl.split('?')[0].replace(/\/$/, '')}/` |
| 217 | } |
| 218 | |
| 219 | async function extractInstagram(targetUrl, options = {}) { |
| 220 | const cleanUrl = normalizeInstagramUrl(targetUrl) |
| 221 | if (cleanUrl.includes('/stories/')) return extractInstagramStories(targetUrl, options) |
| 222 | |
| 223 | const res = await axios.get(cleanUrl, { |
| 224 | headers: DEFAULT_HEADERS, |
| 225 | timeout: 15000, |
| 226 | validateStatus: () => true, |
| 227 | }) |
| 228 | if (res.status === 404) throw new Error('Postingan Instagram tidak ditemukan atau akun diprivat.') |
| 229 | |
| 230 | const $ = cheerio.load(res.data) |
| 231 | const ogTitle = $('meta[property="og:title"]').attr('content') || $('title').text() || '' |
| 232 | const ogDesc = $('meta[property="og:description"]').attr('content') || $('meta[name="description"]').attr('content') || '' |
| 233 | const ogImage = $('meta[property="og:image"]').attr('content') || null |
| 234 | const ogVideo = $('meta[property="og:video"]').attr('content') || $('meta[property="og:video:secure_url"]').attr('content') || null |
| 235 | const jsonBlocks = [] |
| 236 | |
| 237 | $('script').each((_, el) => { |
| 238 | const text = $(el).html() || '' |
| 239 | if (!/xdt_api__v1__media__shortcode__web_info|shortcode_media|video_versions|image_versions2|carousel_media/.test(text)) return |
| 240 | try { |
| 241 | jsonBlocks.push(JSON.parse(text)) |
| 242 | } catch {} |
| 243 | }) |
| 244 | |
| 245 | let mediaItem = null |
| 246 | for (const block of jsonBlocks) { |
| 247 | const items = deepFind(block, (node) => node && typeof node === 'object' && (node.shortcode || node.video_versions || node.carousel_media || node.edge_sidecar_to_children || node.image_versions2)) |
| 248 | if (items.length) { |
| 249 | mediaItem = items[0] |
| 250 | break |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | const author = { |
| 255 | username: mediaItem?.user?.username || ogTitle.match(/@([a-zA-Z0-9_.]+)/)?.[1] || 'instagram_user', |
| 256 | fullName: mediaItem?.user?.full_name || ogTitle.split('on Instagram')[0]?.replace(/^@/, '').trim() || 'Instagram User', |
| 257 | avatar: mediaItem?.user?.profile_pic_url || null, |
| 258 | verified: Boolean(mediaItem?.user?.is_verified), |
| 259 | } |
| 260 | let caption = mediaItem?.caption?.text || '' |
| 261 | if (!caption && ogTitle.includes(': "')) caption = ogTitle.match(/:\s*(?:"|“)([\s\S]*?)(?:"|”)$/i)?.[1] || '' |
| 262 | if (!caption && ogDesc) caption = ogDesc.match(/-\s*(.*)/)?.[1] || '' |
| 263 | |
| 264 | let likes = mediaItem?.like_count || 0 |
| 265 | let comments = mediaItem?.comment_count || 0 |
| 266 | if (!likes && ogDesc) likes = ogDesc.match(/([\d.,]+[kmb]?)\s*likes/i)?.[1] || 0 |
| 267 | if (!comments && ogDesc) comments = ogDesc.match(/([\d.,]+[kmb]?)\s*comments/i)?.[1] || 0 |
| 268 | |
| 269 | let mediaType = 'image' |
| 270 | let videoUrl = null |
| 271 | const images = [] |
| 272 | if (Array.isArray(mediaItem?.carousel_media) && mediaItem.carousel_media.length) { |
| 273 | mediaType = 'carousel' |
| 274 | mediaItem.carousel_media.forEach((item) => { |
| 275 | const video = item.video_versions?.[0]?.url |
| 276 | const image = item.image_versions2?.candidates?.[0]?.url |
| 277 | if (video) images.push({ type: 'video', url: video, thumbnail: image || null }) |
| 278 | else if (image) images.push({ type: 'image', url: image }) |
| 279 | }) |
| 280 | } else if (mediaItem?.edge_sidecar_to_children?.edges) { |
| 281 | mediaType = 'carousel' |
| 282 | mediaItem.edge_sidecar_to_children.edges.forEach(({ node }) => { |
| 283 | const video = node.video_url |
| 284 | const image = node.display_url |
| 285 | if (video) images.push({ type: 'video', url: video, thumbnail: image || null }) |
| 286 | else if (image) images.push({ type: 'image', url: image }) |
| 287 | }) |
| 288 | } |
| 289 | if (mediaType !== 'carousel') { |
| 290 | videoUrl = mediaItem?.video_versions?.[0]?.url || mediaItem?.video_url || ogVideo || null |
| 291 | if (videoUrl || /\/(reel|tv)\//.test(cleanUrl)) mediaType = 'video' |
| 292 | } |
| 293 | if (mediaType !== 'carousel' && mediaType !== 'video') { |
| 294 | const image = mediaItem?.image_versions2?.candidates?.[0]?.url || mediaItem?.display_url || ogImage |
| 295 | if (image) images.push({ type: 'image', url: image }) |
| 296 | } |
| 297 | |
| 298 | return { |
| 299 | status: 'success', source: 'instagram_native_ssr', type: mediaType, url: cleanUrl, |
| 300 | shortcode: mediaItem?.code || mediaItem?.shortcode || cleanUrl.split('/')[4] || '', author, |
| 301 | caption: caption || '-', likes: typeof likes === 'number' ? formatNumber(likes) : likes, |
| 302 | comments: typeof comments === 'number' ? formatNumber(comments) : comments, |
| 303 | takenAt: mediaItem?.taken_at ? new Date(mediaItem.taken_at * 1000).toLocaleString('id-ID') : null, |
| 304 | thumbnail: mediaItem?.image_versions2?.candidates?.[0]?.url || ogImage || null, |
| 305 | videoUrl, images: images.length ? images : ogImage ? [{ type: 'image', url: ogImage }] : [], |
| 306 | } |
| 307 | } |
| 308 | |
| 309 | async function stalkInstagram(target, options = {}) { |
| 310 | if (!target || typeof target !== 'string') throw new Error('Username atau URL profil Instagram wajib diisi!') |
| 311 | const username = target.trim().replace(/^@/, '').replace(/https?:\/\/(?:www\.)?instagram\.com\//i, '').split('/')[0].split('?')[0].trim() |
| 312 | if (!username) throw new Error('Gagal mengekstrak username Instagram dari input.') |
| 313 | const profileUrl = `https://www.instagram.com/${username}/` |
| 314 | const res = await axios.get(profileUrl, { |
| 315 | headers: { 'User-Agent': FACEBOOK_BOT_UA, 'Accept-Language': 'en-US,en;q=0.9' }, |
| 316 | timeout: 15000, |
| 317 | validateStatus: () => true, |
| 318 | }) |
| 319 | if (res.status === 404) throw new Error(`Akun Instagram @${username} tidak ditemukan.`) |
| 320 | const $ = cheerio.load(res.data) |
| 321 | const ogTitle = $('meta[property="og:title"]').attr('content') || '' |
| 322 | const ogDesc = $('meta[property="og:description"]').attr('content') || $('meta[name="description"]').attr('content') || '' |
| 323 | const nameMatch = ogTitle.match(/^(.*?)\s*\(@[a-zA-Z0-9_.]+\)/i) |
| 324 | const followers = ogDesc.match(/([\d.,]+[KkMmBb]?)\s*(?:Followers|Pengikut)/i) |
| 325 | const following = ogDesc.match(/([\d.,]+[KkMmBb]?)\s*(?:Following|Mengikuti)/i) |
| 326 | const posts = ogDesc.match(/([\d.,]+[KkMmBb]?)\s*(?:Posts|Postingan|Kiriman)/i) |
| 327 | const bio = ogDesc.match(/(?:on Instagram|Instagram):\s*(?:"|“)([\s\S]*?)(?:"|”)$/i)?.[1] || ogDesc.match(/-\s*([\s\S]*)/)?.[1] || '-' |
| 328 | return { |
| 329 | status: 'success', username, fullName: nameMatch?.[1]?.trim() || username, bio: bio.trim(), |
| 330 | followers: followers?.[1] || '0', following: following?.[1] || '0', totalPosts: posts?.[1] || '0', |
| 331 | isPrivate: res.data.includes('"is_private":true'), isVerified: res.data.includes('"is_verified":true') || ogTitle.includes('Verified'), |
| 332 | avatarUrl: $('meta[property="og:image"]').attr('content') || null, profileUrl, |
| 333 | ...(options.download ? { note: 'Avatar download tersedia melalui downloadMediaFile().' } : {}), |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | async function extractInstagramStories(target, options = {}) { |
| 338 | const username = target |
| 339 | .trim() |
| 340 | .replace(/^https?:\/\/(?:www\.)?instagram\.com\/stories\//i, '') |
| 341 | .replace(/^https?:\/\/(?:www\.)?instagram\.com\//i, '') |
| 342 | .replace(/\/.*$/, '') |
| 343 | .replace(/^@/, '') |
| 344 | .trim() |
| 345 | if (!username) throw new Error('Username atau URL Instagram Story tidak valid.') |
| 346 | |
| 347 | let stories = [] |
| 348 | let source = null |
| 349 | |
| 350 | try { |
| 351 | const data = await scrapeSaveFreeStories(username, options) |
| 352 | if (Array.isArray(data?.stories) && data.stories.length) { |
| 353 | stories = data.stories |
| 354 | source = 'save_free' |
| 355 | } |
| 356 | } catch (e) { |
| 357 | if (options.debug) console.warn('[Instagram Story] save-free gagal:', e.message) |
| 358 | } |
| 359 | |
| 360 | if (!stories.length) { |
| 361 | try { |
| 362 | const res = await axios.get(`https://api.ryzendesu.vip/api/downloader/igstory?user=${encodeURIComponent(username)}`, { |
| 363 | timeout: 20000, |
| 364 | validateStatus: () => true, |
| 365 | }) |
| 366 | const apiData = res.data |
| 367 | if (apiData && Array.isArray(apiData.data)) { |
| 368 | stories = apiData.data.map((item) => ({ |
| 369 | type: item.type === 'video' || String(item.url || '').includes('.mp4') ? 'video' : 'image', |
| 370 | url: item.url || item, |
| 371 | thumbnail: item.thumbnail || null, |
| 372 | })) |
| 373 | source = 'ryzendesu' |
| 374 | } |
| 375 | } catch (e) { |
| 376 | if (options.debug) console.warn('[Instagram Story] ryzendesu gagal:', e.message) |
| 377 | } |
| 378 | } |
| 379 | |
| 380 | if (!stories.length) { |
| 381 | return { |
| 382 | status: false, |
| 383 | type: 'story', |
| 384 | username, |
| 385 | source, |
| 386 | stories: [], |
| 387 | total: 0, |
| 388 | message: `Tidak ada Story aktif untuk @${username} (akun private / story sudah kadaluarsa).`, |
| 389 | } |
| 390 | } |
| 391 | |
| 392 | const profile = await stalkInstagram(username).catch(() => null) |
| 393 | return { |
| 394 | status: true, |
| 395 | type: 'story', |
| 396 | source, |
| 397 | username, |
| 398 | fullName: profile?.fullName || username, |
| 399 | avatarUrl: profile?.avatarUrl || null, |
| 400 | total: stories.length, |
| 401 | stories, |
| 402 | message: `Ditemukan ${stories.length} Story aktif untuk @${username}.`, |
| 403 | } |
| 404 | } |
| 405 | |
| 406 | async function downloadMediaFile(url, outputPath) { |
| 407 | const dir = path.dirname(outputPath) |
| 408 | if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }) |
| 409 | const res = await axios.get(url, { responseType: 'arraybuffer', headers: { 'User-Agent': DESKTOP_UA, Referer: 'https://www.instagram.com/' }, timeout: 30000 }) |
| 410 | fs.writeFileSync(outputPath, Buffer.from(res.data)) |
| 411 | return outputPath |
| 412 | } |
| 413 | |
| 414 | async function downloadInstagram(targetUrl, options = {}) { |
| 415 | const result = await extractInstagram(targetUrl, options) |
| 416 | const outDir = options.outputDir || path.join(process.cwd(), 'downloads', 'instagram') |
| 417 | const prefix = result.shortcode || result.username || Date.now() |
| 418 | const downloadedFiles = [] |
| 419 | let files = [] |
| 420 | if (result.type === 'story' && Array.isArray(result.stories)) { |
| 421 | files = result.stories.map((item, index) => ({ type: item.type, url: item.url, name: `${prefix}_story_${index + 1}.${item.type === 'video' ? 'mp4' : 'jpg'}` })) |
| 422 | } else if (result.type === 'video' && result.videoUrl) { |
| 423 | files = [{ type: 'video', url: result.videoUrl, name: `${prefix}_video.mp4` }] |
| 424 | } else if (Array.isArray(result.images)) { |
| 425 | files = result.images.map((item, index) => ({ type: item.type, url: item.url, name: `${prefix}_item_${index + 1}.${item.type === 'video' ? 'mp4' : 'jpg'}` })) |
| 426 | } |
| 427 | for (const file of files) downloadedFiles.push({ type: file.type, path: await downloadMediaFile(file.url, path.join(outDir, file.name)) }) |
| 428 | return { ...result, downloadedFiles } |
| 429 | } |
| 430 | |
| 431 | const instagram = extractInstagram |
| 432 | const ig = extractInstagram |
| 433 | const stalk = stalkInstagram |
| 434 | |
| 435 | const isMain = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url)) |
| 436 | if (isMain) { |
| 437 | const [command, target, ...flags] = process.argv.slice(2) |
| 438 | if (!command || flags.includes('--help') || flags.includes('-h')) { |
| 439 | console.log('Pakai: node system/lib/scraper/instagram.js <URL> [--download] | stalk <username> | story <username>') |
| 440 | } else { |
| 441 | const shouldDownload = flags.includes('--download') || flags.includes('-d') |
| 442 | const run = command === 'stalk' |
| 443 | ? stalkInstagram(target) |
| 444 | : command === 'story' || command === 'stories' |
| 445 | ? shouldDownload |
| 446 | ? downloadInstagram(`https://www.instagram.com/stories/${target}/`) |
| 447 | : extractInstagramStories(target) |
| 448 | : shouldDownload |
| 449 | ? downloadInstagram(command) |
| 450 | : extractInstagram(command) |
| 451 | run.then((result) => console.log(JSON.stringify(result, null, 2))).catch((error) => { |
| 452 | console.error('[ERROR]', error.message) |
| 453 | process.exitCode = 1 |
| 454 | }) |
| 455 | } |
| 456 | } |
| 457 | |
| 458 | if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { |
| 459 | const __exports = { extractInstagram: extractInstagram, stalkInstagram: stalkInstagram, extractInstagramStories: extractInstagramStories, downloadMediaFile: downloadMediaFile, downloadInstagram: downloadInstagram, normalizeInstagramUrl: normalizeInstagramUrl, instagram: instagram, ig: ig, stalk: stalk } |
| 460 | const __fns = Object.fromEntries(Object.entries(__exports).filter(([, v]) => typeof v === 'function')) |
| 461 | const __names = Object.keys(__fns) |
| 462 | if (__names.length === 0) { |
| 463 | console.log('Tidak ada fungsi yang bisa di-demo. Import modul ini dari kode lain.') |
| 464 | } else { |
| 465 | const __search = __names.find((n) => /search|cari/i.test(n)) |
| 466 | const __name = __search || __names[0] |
| 467 | const __fn = __fns[__name] |
| 468 | const __isClass = /^class\s/.test(Function.prototype.toString.call(__fn)) |
| 469 | const __defaultArgs = (() => { |
| 470 | const n = __name.toLowerCase() |
| 471 | if (/emoji/.test(n)) return [{ emoji: '😀😃' }] |
| 472 | if (/textpro|generate.*text|effect/.test(n)) return ['neon', 'Hello'] |
| 473 | if (/image|wallpaper|search|query|anime|movie|manga|news|lyrics|wiki|quote|stalk|detail/.test(n)) return ['naruto'] |
| 474 | if (/download|youtube|video|audio|music|spotify|tiktok|instagram|facebook|twitter|reels|terabox|media/.test(n)) return ['https://www.youtube.com/watch?v=dQw4w9WgXcQ'] |
| 475 | if (/dork/.test(n)) return ['example.com', 'documents'] |
| 476 | if (/tts|speech|prompt|chat|ask|translate/.test(n)) return ['Hello world'] |
| 477 | if (/parse|format|normalize|list|features/.test(n)) return [] |
| 478 | return ['test'] |
| 479 | })() |
| 480 | let __args = __defaultArgs |
| 481 | if (process.argv.length > 2) __args = process.argv.slice(2).map((a) => { try { return JSON.parse(a) } catch { return a } }) |
| 482 | if (__args.length === 0 && !__search && !__isClass && __fn.length > 0) { |
| 483 | console.log('Fungsi: ' + __names.join(', ')) |
| 484 | console.log('Contoh: node ' + "instagram.js" + ' ' + __name + ' <arg>') |
| 485 | } else { |
| 486 | ;(async () => { |
| 487 | try { |
| 488 | const r = __isClass ? new __fn(...__args) : await __fn(...__args) |
| 489 | console.log(JSON.stringify(r, null, 2)) |
| 490 | } catch (e) { |
| 491 | console.error('Error: ' + (e && e.message ? e.message : e)) |
| 492 | process.exitCode = 1 |
| 493 | } |
| 494 | })() |
| 495 | } |
| 496 | } |
| 497 | } |
| 498 | |
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.