UNPKG

reelflow

Version:

Elegant and powerful Instagram video downloader for seamless content extraction

155 lines (154 loc) 6.77 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.InstagramDownloader = void 0; const axios_1 = __importDefault(require("axios")); const cheerio_1 = require("cheerio"); const types_js_1 = require('./types.cjs'); const utils_js_1 = require('./utils.cjs'); const constants_js_1 = require('./constants.cjs'); class InstagramDownloader { async getPostPageHTML(postId) { try { const response = await axios_1.default.get(`${constants_js_1.INSTAGRAM_BASE_URL}${constants_js_1.INSTAGRAM_ENDPOINTS.POST}/${postId}`, { headers: constants_js_1.WEBPAGE_HEADERS }); return response.data; } catch (error) { if (axios_1.default.isAxiosError(error)) { throw new types_js_1.InstagramError(error.response?.data?.message || 'Failed to fetch Instagram page', error.response?.status || 500); } throw error; } } async getPostGraphqlData(postId) { try { const encodedData = (0, utils_js_1.encodeGraphqlRequestData)(postId); const response = await axios_1.default.post(`${constants_js_1.INSTAGRAM_BASE_URL}${constants_js_1.INSTAGRAM_ENDPOINTS.GRAPHQL}`, encodedData, { headers: constants_js_1.GRAPHQL_HEADERS }); return response.data; } catch (error) { if (axios_1.default.isAxiosError(error)) { throw new types_js_1.InstagramError(error.response?.data?.message || 'Failed to fetch GraphQL data', error.response?.status || 500); } throw error; } } async getVideoInfoFromHTML(postId) { const html = await this.getPostPageHTML(postId); const $ = (0, cheerio_1.load)(html); return (0, utils_js_1.formatPageJson)($); } async getVideoInfoFromGraphQL(postId) { const data = await this.getPostGraphqlData(postId); const mediaData = data.data?.xdt_shortcode_media; if (!mediaData) { return null; } if (!mediaData.is_video) { throw new types_js_1.InstagramError('This post is not a video', 400); } return (0, utils_js_1.formatGraphqlJson)(mediaData); } async getVideoInfo(url) { const validationError = (0, utils_js_1.validateInstagramURL)(url); if (validationError) { throw new types_js_1.InstagramError(validationError, 400); } const postId = (0, utils_js_1.getPostIdFromUrl)(url); if (!postId) { throw new types_js_1.InstagramError('Could not extract post ID from URL', 400); } // Get the HTML first to extract media info const html = await this.getPostPageHTML(postId); const mediaInfo = await this.extractMediaInfo(html); // Try webpage method first let videoInfo = await this.getVideoInfoFromHTML(postId); if (videoInfo) { return { ...videoInfo, username: mediaInfo.username, description: mediaInfo.description, thumbnail: mediaInfo.thumbnail }; } // Fallback to GraphQL method videoInfo = await this.getVideoInfoFromGraphQL(postId); if (videoInfo) { return { ...videoInfo, username: mediaInfo.username, description: mediaInfo.description, thumbnail: mediaInfo.thumbnail }; } throw new types_js_1.InstagramError('Could not fetch video information', 404); } async extractMediaInfo(html) { try { const $ = (0, cheerio_1.load)(html); // Get thumbnail URL const thumbnail = $('meta[property="og:image"]').attr('content') || undefined; // Get description and username from meta description const rawDescription = $('meta[property="og:description"]').attr('content') || ''; let description = undefined; let username = undefined; // Try to extract username from likes format first (e.g., "2,420 likes, 13 comments - almas.graphi on...") const likesMatch = rawDescription.match(/- (.*?) on/); // Match everything between "- " and " on" if (likesMatch && likesMatch[1]) { // Get the username part and remove any trailing spaces username = likesMatch[1].replace(/\s+$/, ''); // Extract description by removing the likes/comments/date part const descMatch = rawDescription.match(/^[^-]+ - [^\ ]+ on [^‎]+‎(.+)$/); if (descMatch) { // Remove quotes and trim description = descMatch[1].trim().replace(/^["']|["']$/g, '') || undefined; } } else { // Fallback: try to extract username from description format (username: description) const usernameMatch = rawDescription.match(/^([^:]+):/); if (usernameMatch) { username = usernameMatch[1].split(' on ')[0].trim(); // Remove username from description, quotes, and trim description = rawDescription.replace(/^[^:]+:\s*/, '').trim().replace(/^["']|["']$/g, '') || undefined; } } // Extract video or image URL const videoUrl = $('meta[property="og:video"]').attr('content'); const videoWidth = $('meta[property="og:video:width"]').attr('content'); const videoHeight = $('meta[property="og:video:height"]').attr('content'); if (videoUrl && videoWidth && videoHeight) { return { url: videoUrl, type: 'video', width: videoWidth, height: videoHeight, username, thumbnail, description, }; } const imageUrl = $('meta[property="og:image"]').attr('content'); if (imageUrl) { return { url: imageUrl, type: 'image', username, thumbnail, description, }; } throw new Error('No media URL found'); } catch (error) { throw new Error(`Failed to extract media info: ${error.message}`); } } } exports.InstagramDownloader = InstagramDownloader;