Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New process to upload a broadcast replay to IGTV #1364

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions examples/live-replay-to-igtv.example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/* tslint:disable:no-console */
import {IgApiClient, LiveEntity} from '../src';
import Bluebird = require('bluebird');
const pngToJpeg = require('png-to-jpeg')
const sharp = require('sharp');
const axios = require('axios');

const ig = new IgApiClient();

async function login() {
ig.state.generateDevice(process.env.IG_USERNAME);
ig.state.proxyUrl = process.env.IG_PROXY;
await ig.account.login(process.env.IG_USERNAME, process.env.IG_PASSWORD);
}

(async () => {
// basic login-procedure
await login();

const {broadcast_id, upload_url} = await ig.live.create({
// create a stream in 720x1280 (9:16)
previewWidth: 720,
previewHeight: 1280,
// this message is not necessary, because it doesn't show up in the notification
message: 'My message',
});
// (optional) get the key and url for programs such as OBS
const {stream_key, stream_url} = LiveEntity.getUrlAndKey({broadcast_id, upload_url});
console.log(`Start your stream on ${stream_url}.\n
Your key is: ${stream_key}`);

/**
* make sure you are streaming to the url
* the next step will send a notification / start your stream for everyone to see
*/
const startInfo = await ig.live.start(broadcast_id);
// status should be 'ok'
console.log(startInfo);

/**
* now, your stream is running
* the next step is to get comments
* note: comments can only be requested roughly every 2s
*/

// initial comment-timestamp = 0, get all comments
let lastCommentTs = await printComments(broadcast_id, 0);

// enable the comments
await ig.live.unmuteComment(broadcast_id);
/**
* wait 2 seconds until the next request.
* in the real world you'd use something like setInterval() instead of Bluebird.delay() / just to simulate a delay
*/
// wait 2s
await Bluebird.delay(2000);
// now, we print the next comments
lastCommentTs = await printComments(broadcast_id, lastCommentTs);

// now we're commenting on our stream
await ig.live.comment(broadcast_id, 'A comment');

/**
* now, your stream is running, you entertain your followers, but you're tired and
* we're going to stop the stream
*/
await ig.live.endBroadcast(broadcast_id);

// Get live thumbnails, required to post on IGTV
let data = await ig.live.getPostLiveThumbnails(broadcast_id);

// Use an HTTP client to download any thumb
let {data: file} = await axios.get(data.thumbnails[0], {responseType: 'arraybuffer'});

// (optional) Resize thumb to a vertical one and convert to jpg
file = await sharp(file)
.resize({width: 720, height: 1280})
.jpeg({
quality: 100,
})
.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,
title: 'A title',
caption: 'A description',
igtv_share_preview_to_feed: '1',
}, 2000)

console.log(`Live posted to IGTV : ${igtv.upload_id}`));
// now you're basically done
})();

async function printComments(broadcastId, lastCommentTs) {
const {comments} = await ig.live.getComment({broadcastId, lastCommentTs});
if (comments.length > 0) {
comments.forEach(comment => console.log(`${comment.user.username}: ${comment.text}`));
return comments[comments.length - 1].created_at;
} else {
return lastCommentTs;
}
}
31 changes: 0 additions & 31 deletions src/repositories/live.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
LiveSwitchCommentsResponseRootObject,
LiveCreateBroadcastResponseRootObject,
LiveStartBroadcastResponseRootObject,
LiveAddPostLiveToIgtvResponseRootObject,
LiveCommentsResponseRootObject,
LiveHeartbeatViewerCountResponseRootObject,
LiveInfoResponseRootObject,
Expand Down Expand Up @@ -281,36 +280,6 @@ export class LiveRepository extends Repository {
return body;
}

public async addPostLiveToIgtv({
broadcastId,
title,
description,
coverUploadId,
igtvSharePreviewToFeed = false,
}: {
broadcastId: string;
title: string;
description: string;
coverUploadId: string;
igtvSharePreviewToFeed?: boolean;
}): Promise<LiveAddPostLiveToIgtvResponseRootObject> {
const { body } = await this.client.request.send<LiveAddPostLiveToIgtvResponseRootObject>({
url: `/api/v1/live/add_post_live_to_igtv/`,
method: 'POST',
form: this.client.request.sign({
_csrftoken: this.client.state.cookieCsrfToken,
_uuid: this.client.state.uuid,
broadcast_id: broadcastId,
cover_upload_id: coverUploadId,
description: description,
title: title,
internal_only: false,
igtv_share_preview_to_feed: igtvSharePreviewToFeed,
}),
});
return body;
}

public async endBroadcast(broadcastId: string, endAfterCopyrightWarning: boolean = false) {
const { body } = await this.client.request.send({
url: `/api/v1/live/${broadcastId}/end_broadcast/`,
Expand Down
53 changes: 35 additions & 18 deletions src/repositories/media.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ 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<MediaInfoResponseRootObject> {
Expand Down Expand Up @@ -576,7 +577,7 @@ export class MediaRepository extends Repository {
return body;
}

public async configureToIgtv(options: MediaConfigureToIgtvOptions) {
public async configureToIgtv(options: MediaConfigureToIgtvOptions, retryDelay: number = 1000) {
const form: MediaConfigureToIgtvOptions = defaultsDeep(options, {
caption: '',
date_time_original: new Date().toISOString().replace(/[-:]/g, ''),
Expand All @@ -598,23 +599,39 @@ export class MediaRepository extends Repository {
});
const retryContext = options.retryContext;
delete form.retryContext;
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,
}),
});

let body = null;
let response = null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable doesn't need to be here. It can live in the while body.

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,
}),
});

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;
}

Expand Down
4 changes: 4 additions & 0 deletions src/repositories/upload.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ export class UploadRepository extends Repository {
if (options.isSidecar) {
ruploadParams.is_sidecar = '1';
}
if (options.broadcastId) {
ruploadParams.broadcast_id = options.broadcastId;
ruploadParams.is_post_live_igtv = '1';
}
return ruploadParams;
}

Expand Down
1 change: 0 additions & 1 deletion src/responses/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export * from './live.like-count.response';
export * from './live.post-live-thumbnails.response';
export * from './live.like.response';
export * from './live.start-broadcast.response';
export * from './live.add-post-live-to-igtv.response';
export * from './live.switch-comments.response';
export * from './live.viewer-list.response';
export * from './live.add-to-post.response';
Expand Down
5 changes: 0 additions & 5 deletions src/responses/live.add-post-live-to-igtv.response.ts

This file was deleted.

1 change: 1 addition & 0 deletions src/types/upload.photo.options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export interface UploadPhotoOptions {
file: Buffer;
isSidecar?: boolean;
waterfallId?: string;
broadcastId?: string;
}