From 2fedb9a29f68ef0e1da40f52d192824a34a243f1 Mon Sep 17 00:00:00 2001 From: Olivier Mouren Date: Fri, 5 Feb 2021 22:16:37 +0100 Subject: [PATCH] Use publish service --- examples/live-replay-to-igtv.example.ts | 13 +++-- src/errors/ig-upload-live-igtv-error.ts | 9 ++++ src/errors/index.ts | 1 + src/repositories/media.repository.ts | 51 +++++++------------ src/services/publish.service.ts | 52 +++++++++++++++++++- src/types/media.configure-to-igtv.options.ts | 2 +- src/types/posting.live-igtv.options.ts | 8 +++ 7 files changed, 94 insertions(+), 42 deletions(-) create mode 100644 src/errors/ig-upload-live-igtv-error.ts create mode 100644 src/types/posting.live-igtv.options.ts diff --git a/examples/live-replay-to-igtv.example.ts b/examples/live-replay-to-igtv.example.ts index 9d64ae430..12b558955 100644 --- a/examples/live-replay-to-igtv.example.ts +++ b/examples/live-replay-to-igtv.example.ts @@ -81,16 +81,15 @@ async function login() { .toBuffer(); // Upload the thumbnail with a broadcast id for a replay and get uploadId - let upload = await ig.upload.photo({file, broadcastId: broadcast_id}); - - let igtv = await ig.media.configureToIgtv({ - upload_id: upload.upload_id, + let igtv = await ig.publish.liveIgtv({ + file, + broadcastId: broadcast_id, title: 'A title', caption: 'A description', - igtv_share_preview_to_feed: '1', - }, 2000) + igtv_share_preview_to_feed: '1' + }); - console.log(`Live posted to IGTV : ${igtv.upload_id}`)); + console.log(`Live posted to IGTV : ${igtv.upload_id}`); // now you're basically done })(); diff --git a/src/errors/ig-upload-live-igtv-error.ts b/src/errors/ig-upload-live-igtv-error.ts new file mode 100644 index 000000000..8dbf2bec0 --- /dev/null +++ b/src/errors/ig-upload-live-igtv-error.ts @@ -0,0 +1,9 @@ +import { IgResponseError } from './ig-response.error'; +import { IgResponse } from '../types'; +import { UploadRepositoryVideoResponseRootObject } from '../responses'; + +export class IgUploadLiveIgtvError extends IgResponseError { + constructor(response: IgResponse) { + super(response); + } +} diff --git a/src/errors/index.ts b/src/errors/index.ts index bdf3fe24d..e41d3cf41 100644 --- a/src/errors/index.ts +++ b/src/errors/index.ts @@ -22,5 +22,6 @@ export * from './ig-challenge-wrong-code.error'; export * from './ig-exact-user-not-found-error'; export * from './ig-user-id-not-found.error'; export * from './ig-upload-video-error'; +export * from './ig-upload-live-igtv-error'; export * from './ig-user-has-logged-out.error'; export * from './ig-configure-video-error'; diff --git a/src/repositories/media.repository.ts b/src/repositories/media.repository.ts index e9fe9f080..40a2a21b6 100644 --- a/src/repositories/media.repository.ts +++ b/src/repositories/media.repository.ts @@ -30,7 +30,6 @@ import { MediaRepositoryConfigureResponseRootObject } from '../responses'; import Chance = require('chance'); import { MediaRepositoryCheckOffensiveCommentResponseRootObject } from '../responses'; import { StoryMusicQuestionResponse, StoryTextQuestionResponse } from '../types/story-response.options'; -import { IgResponseError } from "../errors"; export class MediaRepository extends Repository { public async info(mediaId: string): Promise { @@ -577,7 +576,7 @@ export class MediaRepository extends Repository { return body; } - public async configureToIgtv(options: MediaConfigureToIgtvOptions, retryDelay: number = 1000) { + public async configureToIgtv(options: MediaConfigureToIgtvOptions) { const form: MediaConfigureToIgtvOptions = defaultsDeep(options, { caption: '', date_time_original: new Date().toISOString().replace(/[-:]/g, ''), @@ -600,38 +599,24 @@ export class MediaRepository extends Repository { const retryContext = options.retryContext; delete form.retryContext; - let body = null; - let response = null; - while (!body) { - try { - response = await this.client.request.send({ - url: '/api/v1/media/configure_to_igtv/', - method: 'POST', - qs: { - video: '1', - }, - headers: { - is_igtv_video: '1', - retry_context: JSON.stringify(retryContext), - }, - form: this.client.request.sign({ - ...form, - _csrftoken: this.client.state.cookieCsrfToken, - _uid: this.client.state.cookieUserId, - _uuid: this.client.state.uuid, - }), - }); + const { body } = await this.client.request.send({ + url: '/api/v1/media/configure_to_igtv/', + method: 'POST', + qs: { + video: '1', + }, + headers: { + is_igtv_video: '1', + retry_context: JSON.stringify(retryContext), + }, + form: this.client.request.sign({ + ...form, + _csrftoken: this.client.state.cookieCsrfToken, + _uid: this.client.state.cookieUserId, + _uuid: this.client.state.uuid, + }), + }); - body = response.body; - } catch (e) { - // Endpoint can return an error "202 Accepted; Transcode not finished yet" if Instagram has not finished to process the upload, retry after a delay - if (!(e instanceof IgResponseError && e.response.statusCode === 202)) { - throw e; - } else { - await new Promise(resolve => setTimeout(resolve, retryDelay)); - } - } - } return body; } diff --git a/src/services/publish.service.ts b/src/services/publish.service.ts index 7b55e3ec7..9a4bda008 100644 --- a/src/services/publish.service.ts +++ b/src/services/publish.service.ts @@ -19,9 +19,10 @@ import { UploadVideoOptions, } from '../types'; import { PostingLocation, PostingStoryOptions } from '../types/posting.options'; -import { IgConfigureVideoError, IgResponseError, IgUploadVideoError } from '../errors'; +import { IgConfigureVideoError, IgResponseError, IgUploadLiveIgtvError, IgUploadVideoError } from '../errors'; import { StatusResponse, UploadRepositoryVideoResponseRootObject } from '../responses'; import { PostingIgtvOptions } from '../types/posting.igtv.options'; +import { PostingLiveIgtvOptions } from "../types/posting.live-igtv.options"; import sizeOf = require('image-size'); import Bluebird = require('bluebird'); import Chance = require('chance'); @@ -52,6 +53,22 @@ export class PublishService extends Repository { }; } + /** + * @param transcodeDelayInMs The delay for instagram to transcode the video + */ + public static catchLiveIgtvTranscodeError(transcodeDelayInMs: number) { + 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 IgUploadLiveIgtvError(error.response as IgResponse); + } + }; + } + /** * Gets duration in ms, width and height info for a video in the mp4 container * @param buffer Buffer, containing the video-file @@ -453,6 +470,39 @@ export class PublishService extends Repository { } } + public async liveIgtv(options: PostingLiveIgtvOptions) { + const uploadedPhoto = await this.client.upload.photo({ + file: options.file, + broadcastId: options.broadcastId, + }); + + await Bluebird.try(() => + this.client.media.uploadFinish({ + upload_id: uploadedPhoto.upload_id, + source_type: '4', + }), + ).catch(IgResponseError, PublishService.catchLiveIgtvTranscodeError(options.transcodeDelay || 5000)); + + const configureOptions: MediaConfigureToIgtvOptions = { + upload_id: uploadedPhoto.upload_id, + title: options.title, + caption: options.caption, + igtv_share_preview_to_feed: options.igtv_share_preview_to_feed, + length: 0 + }; + + for (let i = 0; i < 6; i++) { + try { + return await this.client.media.configureToIgtv(configureOptions); + } catch (e) { + if (i >= 5 || e.response.statusCode >= 400) { + throw new IgConfigureVideoError(e.response, configureOptions); + } + await Bluebird.delay((i + 1) * 2 * 1000); + } + } + } + private async regularVideo(options: UploadVideoOptions) { options = defaults(options, { uploadId: Date.now(), diff --git a/src/types/media.configure-to-igtv.options.ts b/src/types/media.configure-to-igtv.options.ts index c2e83041b..aa28edd35 100644 --- a/src/types/media.configure-to-igtv.options.ts +++ b/src/types/media.configure-to-igtv.options.ts @@ -2,7 +2,7 @@ export interface MediaConfigureToIgtvOptions { upload_id: string; title: string; length: number; - extra: { source_width: number; source_height: number }; + extra?: { source_width: number; source_height: number }; caption?: string; // will be converted to a json-string feed_preview_crop?: diff --git a/src/types/posting.live-igtv.options.ts b/src/types/posting.live-igtv.options.ts new file mode 100644 index 000000000..073bcef23 --- /dev/null +++ b/src/types/posting.live-igtv.options.ts @@ -0,0 +1,8 @@ +export interface PostingLiveIgtvOptions { + file: Buffer; + title: string; + caption?: string; + broadcastId: string; + igtv_share_preview_to_feed?: '1' | '0'; + transcodeDelay?: number +} \ No newline at end of file