UNPKG

instagram-private-api

Version:

Instagram private API wrapper for full access to instagram

419 lines 20.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PublishService = void 0; const repository_1 = require("../core/repository"); const types_1 = require("../types"); const errors_1 = require("../errors"); const sizeOf = require("image-size"); const Bluebird = require("bluebird"); const Chance = require("chance"); const lodash_1 = require("lodash"); const upload_repository_1 = require("../repositories/upload.repository"); const debug_1 = require("debug"); const sticker_builder_1 = require("../sticker-builder"); class PublishService extends repository_1.Repository { constructor() { super(...arguments); this.chance = new Chance(); } static catchTranscodeError(videoInfo, transcodeDelayInMs) { return error => { if (error.response.statusCode === 202) { PublishService.publishDebug(`Received trancode error: ${JSON.stringify(error.response.body)}, waiting ${transcodeDelayInMs}ms`); return Bluebird.delay(transcodeDelayInMs); } else { throw new errors_1.IgUploadVideoError(error.response, videoInfo); } }; } static getVideoInfo(buffer) { const width = PublishService.read16(buffer, ['moov', 'trak', 'stbl', 'avc1'], 24); const height = PublishService.read16(buffer, ['moov', 'trak', 'stbl', 'avc1'], 26); return { duration: PublishService.getMP4Duration(buffer), width, height, }; } static getMP4Duration(buffer) { const timescale = PublishService.read32(buffer, ['moov', 'mvhd'], 12); const length = PublishService.read32(buffer, ['moov', 'mvhd'], 12 + 4); return Math.floor((length / timescale) * 1000); } static makeLocationOptions(location) { const options = {}; if (typeof location !== 'undefined') { const { lat, lng, external_id_source, external_id, name, address } = location; options.location = { name, lat, lng, address, external_source: external_id_source, external_id, }; options.location[external_id_source + '_id'] = external_id; options.geotag_enabled = '1'; options.media_latitude = lat.toString(); options.media_longitude = lng.toString(); options.posting_latitude = lat.toString(); options.posting_longitude = lng.toString(); } return options; } static read32(buffer, keys, offset) { let start = 0; for (const key of keys) { start = buffer.indexOf(Buffer.from(key), start) + key.length; } return buffer.readUInt32BE(start + offset); } static read16(buffer, keys, offset) { let start = 0; for (const key of keys) { start = buffer.indexOf(Buffer.from(key), start) + key.length; } return buffer.readUInt16BE(start + offset); } async photo(options) { const uploadedPhoto = await this.client.upload.photo({ file: options.file, }); const imageSize = await sizeOf(options.file); const configureOptions = Object.assign({ upload_id: uploadedPhoto.upload_id, width: imageSize.width, height: imageSize.height, caption: options.caption }, PublishService.makeLocationOptions(options.location)); if (typeof options.usertags !== 'undefined') { configureOptions.usertags = options.usertags; } return await this.client.media.configure(configureOptions); } async video(options) { const uploadId = Date.now().toString(); const videoInfo = PublishService.getVideoInfo(options.video); PublishService.publishDebug(`Publishing video to timeline: ${JSON.stringify(videoInfo)}`); await Bluebird.try(() => this.regularVideo(Object.assign({ video: options.video, uploadId }, videoInfo))).catch(errors_1.IgResponseError, error => { throw new errors_1.IgUploadVideoError(error.response, videoInfo); }); await this.client.upload.photo({ file: options.coverImage, uploadId: uploadId.toString(), }); await Bluebird.try(() => this.client.media.uploadFinish({ upload_id: uploadId, source_type: '4', video: { length: videoInfo.duration / 1000.0 }, })).catch(errors_1.IgResponseError, PublishService.catchTranscodeError(videoInfo, options.transcodeDelay || 5000)); const configureOptions = Object.assign({ upload_id: uploadId.toString(), caption: options.caption, length: videoInfo.duration / 1000.0, width: videoInfo.width, height: videoInfo.height, clips: [ { length: videoInfo.duration / 1000.0, source_type: '4', }, ] }, PublishService.makeLocationOptions(options.location)); if (typeof options.usertags !== 'undefined') { configureOptions.usertags = options.usertags; } for (let i = 0; i < 6; i++) { try { return await this.client.media.configureVideo(configureOptions); } catch (e) { if (i >= 5 || e.response.statusCode >= 400) { throw new errors_1.IgConfigureVideoError(e.response, configureOptions); } await Bluebird.delay((i + 1) * 2 * 1000); } } } async album(options) { const isPhoto = (arg) => arg.file !== undefined; const isVideo = (arg) => arg.video !== undefined; for (const item of options.items) { if (isPhoto(item)) { const uploadedPhoto = await this.client.upload.photo({ file: item.file, uploadId: item.uploadId, isSidecar: true, }); const { width, height } = await sizeOf(item.file); item.width = width; item.height = height; item.uploadId = uploadedPhoto.upload_id; } else if (isVideo(item)) { item.videoInfo = PublishService.getVideoInfo(item.video); item.uploadId = Date.now().toString(); PublishService.publishDebug(`Adding video to album: ${JSON.stringify(item.videoInfo)}`); await Bluebird.try(() => this.regularVideo(Object.assign({ video: item.video, uploadId: item.uploadId, isSidecar: true }, item.videoInfo))).catch(errors_1.IgResponseError, error => { throw new errors_1.IgConfigureVideoError(error.response, item.videoInfo); }); await this.client.upload.photo({ file: item.coverImage, uploadId: item.uploadId, isSidecar: true, }); await Bluebird.try(() => this.client.media.uploadFinish({ upload_id: item.uploadId, source_type: '4', video: { length: item.videoInfo.duration / 1000.0 }, })).catch(errors_1.IgResponseError, PublishService.catchTranscodeError(item.videoInfo, item.transcodeDelay)); } } return await this.client.media.configureSidecar(Object.assign({ caption: options.caption, children_metadata: options.items.map(item => { if (isVideo(item)) { return { upload_id: item.uploadId, width: item.videoInfo.width, height: item.videoInfo.height, length: item.videoInfo.duration, usertags: item.usertags, }; } else if (isPhoto(item)) { return { upload_id: item.uploadId, width: item.width, height: item.height, usertags: item.usertags, }; } }) }, PublishService.makeLocationOptions(options.location))); } async story(options) { const isPhoto = (arg) => arg.file !== undefined; if (options.stickerConfig instanceof sticker_builder_1.StickerBuilder) { options.stickerConfig = options.stickerConfig.build(); } const storyStickerIds = []; const configureOptions = Object.assign({ configure_mode: '1' }, (options.stickerConfig ? options.stickerConfig : {})); const uploadAndConfigure = () => isPhoto(options) ? this.uploadAndConfigureStoryPhoto(options, configureOptions) : this.uploadAndConfigureStoryVideo(options, configureOptions); const threadIds = typeof options.threadIds !== 'undefined'; const recipients = typeof options.recipientUsers !== 'undefined'; if (recipients || threadIds) { configureOptions.configure_mode = '2'; configureOptions.view_mode = options.viewMode; configureOptions.reply_type = options.replyType; configureOptions.client_context = this.chance.guid(); if (recipients) { configureOptions.recipient_users = options.recipientUsers; } configureOptions.thread_ids = threadIds ? options.threadIds : []; return uploadAndConfigure(); } if (options.toBesties) { configureOptions.audience = 'besties'; } if (typeof options.hashtags !== 'undefined' && options.hashtags.length > 0) { if (typeof options.caption === 'undefined') { options.caption = ''; } options.hashtags.forEach(hashtag => { if (hashtag.tag_name.includes('#')) { hashtag.tag_name = hashtag.tag_name.replace('#', ''); } if (!options.caption.includes(hashtag.tag_name)) { options.caption = `${options.caption} ${hashtag.tag_name}`; } }); configureOptions.story_hashtags = options.hashtags; configureOptions.mas_opt_in = 'NOT_PROMPTED'; } if (typeof options.location !== 'undefined') { const { latitude, longitude } = options.location; configureOptions.geotag_enabled = '1'; configureOptions.posting_latitude = latitude; configureOptions.posting_longitude = longitude; configureOptions.media_latitude = latitude; configureOptions.media_longitude = longitude; configureOptions.story_locations = [options.location.sticker]; configureOptions.mas_opt_in = 'NOT_PROMPTED'; } if (typeof options.mentions !== 'undefined' && options.mentions.length > 0) { if (typeof options.caption === 'undefined') { options.caption = ''; } else { options.caption = options.caption.replace(' ', '+') + '+'; } configureOptions.reel_mentions = options.mentions; configureOptions.mas_opt_in = 'NOT_PROMPTED'; } if (typeof options.poll !== 'undefined') { configureOptions.story_polls = [options.poll]; configureOptions.internal_features = 'polling_sticker'; configureOptions.mas_opt_in = 'NOT_PROMPTED'; } if (typeof options.slider !== 'undefined') { configureOptions.story_sliders = [options.slider]; storyStickerIds.push(`emoji_slider_${options.slider.emoji}`); } if (typeof options.question !== 'undefined') { configureOptions.story_questions = [options.question]; storyStickerIds.push('question_sticker_ma'); } if (typeof options.countdown !== 'undefined') { configureOptions.story_countdowns = [options.countdown]; storyStickerIds.push('countdown_sticker_time'); } if (typeof options.media !== 'undefined') { configureOptions.attached_media = [options.media]; storyStickerIds.push(`media_simple_${options.media.media_id}`); } if (typeof options.chat !== 'undefined') { configureOptions.story_chats = [options.chat]; storyStickerIds.push('chat_sticker_id'); } if (typeof options.quiz !== 'undefined') { configureOptions.story_quizs = [options.quiz]; storyStickerIds.push('quiz_story_sticker_default'); } if (typeof options.link !== 'undefined' && options.link.length > 0) { configureOptions.story_cta = [ { links: [{ webUri: options.link }], }, ]; } if (storyStickerIds.length > 0) { configureOptions.story_sticker_ids = storyStickerIds.join(','); } return uploadAndConfigure(); } async igtvVideo(options) { const videoInfo = PublishService.getVideoInfo(options.video); PublishService.publishDebug(`Publishing video to igtv: ${JSON.stringify(videoInfo)}`); const uploadId = Date.now().toString(); const uploadResult = await this.segmentedVideo(Object.assign(Object.assign(Object.assign({ video: options.video, isIgtvVideo: true }, videoInfo), { uploadId }), options.uploadOptions)); await this.client.upload.photo({ uploadId, file: options.coverFrame }); const form = { upload_id: uploadId, title: options.title, caption: options.caption, audio_muted: options.audioMuted, length: videoInfo.duration / 1000.0, extra: { source_width: videoInfo.width, source_height: videoInfo.height, }, retryContext: uploadResult.retryContext, }; if (options.shareToFeed) { form.igtv_share_preview_to_feed = '1'; if (options.feedPreviewCrop) { const { left, right, top, bottom } = options.feedPreviewCrop; form.feed_preview_crop = { crop_left: left, crop_bottom: bottom, crop_right: right, crop_top: top }; } else { const ratio = videoInfo.width / videoInfo.height; if (ratio > 1) { throw new Error('Received invalid video ratio. Try specifying feedPreviewCrop directly.'); } form.feed_preview_crop = { crop_left: 0.0, crop_right: 1.0, crop_top: (1 - ratio) / 2, crop_bottom: ratio + (1 - ratio) / 2, }; } } const finalInput = Object.assign(Object.assign({}, form), options.configureOptions); for (let i = 0; i < 6; i++) { try { return await this.client.media.configureToIgtv(finalInput); } catch (e) { if (i >= 6) { throw new errors_1.IgConfigureVideoError(e.response, finalInput); } await Bluebird.delay((i + 1) * 2 * 1000); } } } async regularVideo(options) { options = (0, lodash_1.defaults)(options, { uploadId: Date.now(), waterfallId: this.chance.guid({ version: 4 }), }); options.uploadName = options.uploadName || `${options.uploadId}_0_${(0, lodash_1.random)(1000000000, 9999999999)}`; const ruploadParams = upload_repository_1.UploadRepository.createVideoRuploadParams(options, options.uploadId); const { offset } = await this.client.upload.initVideo({ name: options.uploadName, ruploadParams, waterfallId: options.waterfallId, }); return this.client.upload.video(Object.assign({ offset }, options)); } async segmentedVideo(options) { const uploadId = options.uploadId || Date.now().toString(); const retryContext = options.retryContext || { num_step_auto_retry: 0, num_reupload: 0, num_step_manual_retry: 0 }; const ruploadParams = upload_repository_1.UploadRepository.createVideoRuploadParams(options, uploadId, retryContext); const waterfallId = options.waterfallId || (0, lodash_1.random)(1000000000, 9999999999).toString(); const { stream_id: streamId } = await this.client.upload.startSegmentedVideo(ruploadParams); const segments = options.segments || (options.segmentDivider || types_1.SEGMENT_DIVIDERS.sectionSize(Math.pow(2, 24)))({ buffer: options.video, client: this.client, }); PublishService.publishDebug(`Uploading ${segments.length} segments.`); let startOffset = 0; for (const segment of segments) { const transferId = `${this.chance.guid({ version: 4 }).replace('-', '')}-0-${segment.byteLength}`; const { offset: streamOffset } = await this.client.upload.videoSegmentInit({ waterfallId, streamId, startOffset, ruploadParams, transferId, }); if (streamOffset !== 0) { throw new Error(`Offset != 0 isn't implemented. Open an issue including your network config and other setup information to reproduce.`); } await this.client.upload.videoSegmentTransfer({ waterfallId, streamId, startOffset, ruploadParams, transferId, segment, }); startOffset += segment.byteLength; } const end = await this.client.upload.endSegmentedVideo({ ruploadParams, streamId }); return Object.assign(Object.assign({}, end), { retryContext, uploadId, waterfallId }); } async uploadAndConfigureStoryPhoto(options, configureOptions) { const uploadId = Date.now().toString(); const imageSize = await sizeOf(options.file); await this.client.upload.photo({ file: options.file, uploadId, }); return await this.client.media.configureToStory(Object.assign(Object.assign({}, configureOptions), { upload_id: uploadId, width: imageSize.width, height: imageSize.height })); } async uploadAndConfigureStoryVideo(options, configureOptions) { const uploadId = (0, lodash_1.random)(100000000000, 999999999999).toString(); const videoInfo = PublishService.getVideoInfo(options.video); PublishService.publishDebug(`Publishing video to story: ${JSON.stringify(videoInfo)}`); const waterfallId = this.chance.guid({ version: 4 }); await Bluebird.try(() => this.regularVideo(Object.assign({ video: options.video, uploadId, forDirectStory: configureOptions.configure_mode === '2', waterfallId, forAlbum: true }, videoInfo))).catch(errors_1.IgResponseError, error => { throw new errors_1.IgConfigureVideoError(error.response, videoInfo); }); await this.client.upload.photo({ file: options.coverImage, waterfallId, uploadId, }); await Bluebird.try(() => this.client.media.uploadFinish({ upload_id: uploadId, source_type: '3', video: { length: videoInfo.duration / 1000.0 }, })).catch(errors_1.IgResponseError, PublishService.catchTranscodeError(videoInfo, options.transcodeDelay)); return Bluebird.try(() => this.client.media.configureToStoryVideo(Object.assign({ upload_id: uploadId, length: videoInfo.duration / 1000.0, width: videoInfo.width, height: videoInfo.height }, configureOptions))).catch(errors_1.IgResponseError, error => { throw new errors_1.IgConfigureVideoError(error.response, videoInfo); }); } } exports.PublishService = PublishService; PublishService.publishDebug = (0, debug_1.default)('ig:publish'); //# sourceMappingURL=publish.service.js.map