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 1 commit
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
129 changes: 129 additions & 0 deletions examples/live-replay-to-igtv.example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/* tslint:disable:no-console */
import {IgApiClient, LiveEntity} from '../src';
import Bluebird = require('bluebird');
const pngToJpeg = require('png-to-jpeg')
const sharp = require('sharp');
const https = require('https');

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)

// Download any thumb
let file = await new Promise((resolve) => https.get(data.thumbnails[0], (download) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here you could use request-promise as it's already a dependency of this library.

let ds = [];
download.on("data", (d) => ds.push(d));
download.on("end", () => resolve(Buffer.concat(ds)))
}))

// (optional) Resize thumb to a vertical one
file = await sharp(file)
.resize(720, 1280)
.png()
.toBuffer()

// It will be a png, it must be converted to jpg
file = await pngToJpeg({quality: 100})(file)

// 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 = null
let currentRetry = 0
let maxRetry = 3
let retryDelay = 4
while (!igtv) {
// This endpoint can return an error "202 Accepted; Transcode not finished yet" if Instagram has not finished to process the previous upload, so retry later in this case
try {
igtv = await ig.media.configureToIgtv({
upload_id: upload.upload_id,
title: 'A title',
caption: 'A description',
igtv_share_preview_to_feed: '1',
})

console.log(`Live posted to IGTV : ${igtv.upload_id}`))
} catch (e) {
currentRetry++
if (currentRetry > maxRetry) {
throw e
} else {
await (new Promise(resolve => {
setTimeout(resolve, currentRetry * retryDelay)
}))
}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This could live in its own function and not pollute the current function.

For example:

async function retryDelayed<T>(fn: () => Promise<T>, retryOptions: { retries: number, delayMs: number }): Promise<T> {
  let step = 0;
  while(step++ < retryOptions.retries) {
    try {
      return await fn();
    } catch(e) {
      if(step >= retryOptions.retries) throw e;
      
      await new Promise(r => setTimeout(r, step * retryOptions.delayMs));
    }
  }
}

//  used like this:
const user = await retryDelayed(() => ig.user.info(123), {retries: 2, delayMs: 2 * 1000 /* 2s */ });

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the tips @Nerixyz .
But before doing this... I really can't understand, it's not working anymore. I made and tested this PR yesterday, and today, I got a 500 error with the endpoint "/api/v1/media/configure_to_igtv/".
I think that there is a problem with my picture upload because if I use in the configureToIgtv method an existing "upload_id" (that I get from the app directly, so i'm bypassing the "download, convert and upload thumb for broadcast" step), it works.
If you have any idea... You're welcome!

Copy link

Choose a reason for hiding this comment

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

content/site-policy/content-removal-policies/github-private-information-removal-policy.md

Copy link

Choose a reason for hiding this comment

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

Thanks for the tips @Nerixyz .
But before doing this... I really can't understand, it's not working anymore. I made and tested this PR yesterday, and today, I got a 500 error with the endpoint "/api/v1/media/configure_to_igtv/".
I think that there is a problem with my picture upload because if I use in the configureToIgtv method an existing "upload_id" (that I get from the app directly, so i'm bypassing the "download, convert and upload thumb for broadcast" step), it works.
If you have any idea... You're welcome!


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