Skip to content

Commit

Permalink
Add support to publish reels (#109)
Browse files Browse the repository at this point in the history
  • Loading branch information
TiagoGrosso authored Nov 13, 2022
1 parent 162608a commit 0c92c5e
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 6 deletions.
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ This package is made by independent contributors and is in no way officially rel

You can find what changed in each version by checking the [Changelog](changelog/changelog.md).

## New since 1.2.0
## New since 5.0

You can now use this lib to publish videos and photos to your page! Check out the [Publishing media](#publishing-media) section.
You can now use this lib to publish reels to your page! Check out the [Publishing media](#publishing-media) section.

## Installation

Expand Down Expand Up @@ -92,12 +92,26 @@ import { GetPageInfoRequest, RequestConfig } from 'instagram-graph-api';

Publishing Media through the Instagram Graph API, and conversely through this lib, follows these steps:

1. Create an IG Container object (through the `PostPagePhotoMediaRequest` or the `PostPageVideoMediaRequest`).
1. Create an IG Container object. The request will return the container id.
- For photos use `PostPagePhotoMediaRequest`.
- For videos use `PostPageVideoMediaRequest`.
- For reels use `PostPageReelMediaRequest`.
- For carousels check the [Publishing Carousels section below](#publishing-carousels).
2. Wait for the IG Container status to move to `FINISHED` (check the status through the `GetContainerRequest`).
3. Publish the IG Container (through the `PostPublishMediaRequest`).

For more info on this flow, refer to the [Content Publishing documentation](https://developers.facebook.com/docs/instagram-api/guides/content-publishing).

#### Publishing Carousels

Publishing carousels is similar to posting other media types, but you need to create the child containers first. The steps are:

1. Create photo or video containers (2-10 containers).
2. Wait for the IG Container status to move to `FINISHED`. **Do not publish them!**.
3. Use the container ids of your photo and video containers in a `PostPageCarouselMediaRequest`.
4. Wait for the carousel IG Container status to move to `FINISHED`.
5. Publish the carousel IG Container.

### Other request options

You can give paging and range options to the requests, as supported by certain resources on the Instagram Graph API. (check the [reference documentation](https://developers.facebook.com/docs/instagram-api/reference/) to see which ones do). For example, the Get Page Media request supports paging, here's a naive example of how to use:
Expand Down
4 changes: 3 additions & 1 deletion changelog/next_release.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

### Added

- Moved `MediaTypeInResponse` enum to `Enums.ts`.
- Added `MediaTypeInResponse` enum to `Enums.ts`.
- Support for uploading Reels via the `PostPageReelMediaRequest`.
- Added `REEL` as a possible value of `MediaProductType` and `MediaType`.

### Changed

Expand Down
25 changes: 23 additions & 2 deletions src/it/page/PublishMediaRequests.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ describe('PublishMedia', () => {
it('Publishes carousel media', async () => {
const photoMedia = getRandomPhoto();
const videoMedia = getRandomVideo();
const postPhotoRequest = getClient().newPostPagePhotoMediaRequest(photoMedia.url, photoMedia.caption);
const postVideoRequest = getClient().newPostPageVideoMediaRequest(videoMedia.url, videoMedia.caption);
const postPhotoRequest = getClient()
.newPostPagePhotoMediaRequest(photoMedia.url, photoMedia.caption)
.withIsCarousel(true);
const postVideoRequest = getClient()
.newPostPageVideoMediaRequest(videoMedia.url, videoMedia.caption)
.withIsCarousel(true);

const photoContainerId = await createContainerAndWaitToBeReady(postPhotoRequest);
const videoContainerId = await createContainerAndWaitToBeReady(postVideoRequest);
Expand All @@ -46,6 +50,23 @@ describe('PublishMedia', () => {
expect(response.getMediaType()).toEqual(MediaTypeInResponses.CAROUSEL);
expect(response.getMediaProductType()).toEqual(MediaProductType.FEED);
});

it('Publishes reel media', async () => {
const media = getRandomVideo();
const postReelRequest = getClient().newPostPageReelMediaRequest(media.url, media.caption).withShareToFeed(true);

const containerId = await createContainerAndWaitToBeReady(postReelRequest);

const mediaId = await publishMedia(containerId);
const getMediaRequest = getClient().newGetMediaInfoRequest(mediaId);
const response = await getMediaRequest.execute();

expect(response.getId()).toEqual(mediaId);
expect(response.getCaption()).toEqual(media.caption);
expect(response.getOwnerId()).toEqual(getPageId());
expect(response.getMediaType()).toEqual(MediaTypeInResponses.VIDEO);
expect(response.getMediaProductType()).toEqual(MediaProductType.REEL);
});
});

function waitForContainerToBeReady(containerId: string): Promise<boolean> {
Expand Down
30 changes: 30 additions & 0 deletions src/main/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { GetPageRecentlySearchedHashtagsRequest } from './requests/page/recently
import { GetPageStoriesRequest } from './requests/page/stories/GetPageStoriesRequest';
import { GetTagsRequest } from './requests/page/tags/GetTagsRequest';
import { UserTag } from './requests/Params';
import { PostPageReelMediaRequest } from './requests/page/media/PostPageReelMediaRequest';

/**
* A client that creating requests.
Expand Down Expand Up @@ -430,6 +431,35 @@ export class Client {
).withApiVersion(this.apiVersion);
}

/**
* Build a new {@link PostPageReelMediaRequest}.
*
* @param videoUrl the video URL.
* @param caption the caption.
* @param thumbOffset the thumbnail offset.
* @param shareToFeed whether the reel should be shared in the feed as well.
* @param locationId the location id.
*
* @returns a new {@link PostPageReelMediaRequest}.
*/
public newPostPageReelMediaRequest(
videoUrl: string,
caption?: string,
thumbOffset?: number,
shareToFeed?: boolean,
locationId?: string
): PostPageReelMediaRequest {
return new PostPageReelMediaRequest(
this.accessToken,
this.pageId,
videoUrl,
caption,
thumbOffset,
shareToFeed,
locationId
).withApiVersion(this.apiVersion);
}

/**
* Build a new {@link PostPageCarouselMediaRequest}.
*
Expand Down
4 changes: 4 additions & 0 deletions src/main/Enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ export enum MediaProductType {
FEED = 'FEED',
IGTV = 'IGTV',
STORY = 'STORY',
REEL = 'REELS',
}

/**
Expand Down Expand Up @@ -603,6 +604,9 @@ export enum ApiVersion {
export enum MediaTypeInResponses {
IMAGE = 'IMAGE',

/**
* Applies to both normal videos and reels.
*/
VIDEO = 'VIDEO',

CAROUSEL = 'CAROUSEL_ALBUM',
Expand Down
1 change: 1 addition & 0 deletions src/main/requests/Params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ export interface Params {
*/
location_id?: string;
creation_id?: string;
share_to_feed?: boolean;
}
2 changes: 2 additions & 0 deletions src/main/requests/page/media/AbstractPostPageMediaRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,6 @@ export enum MediaType {
VIDEO = 'VIDEO',

CAROUSEL = 'CAROUSEL',

REEL = 'REELS',
}
66 changes: 66 additions & 0 deletions src/main/requests/page/media/PostPageReelMediaRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { AbstractPostPageMediaRequest, MediaType } from './AbstractPostPageMediaRequest';

/**
* * A request that creates a new Reels Media container.
* *
* * @author Tiago Grosso <tiagogrosso99@gmail.com>
* * @since `next.release`
* */
export class PostPageReelMediaRequest extends AbstractPostPageMediaRequest {
/**
* The constructor
*
* @param accessToken the access token.
* @param pageId the page id.
* @param videoUrl the video URL.
* @param caption the caption.
* @param thumbOffset the thumbnail offset.
* @param shareToFeed whether the reel should be shared in the feed as well.
* @param locationId the location id.
*/
constructor(
accessToken: string,
pageId: string,
videoUrl: string,
caption?: string,
thumbOffset?: number,
shareToFeed?: boolean,
locationId?: string
) {
super(accessToken, pageId, caption, locationId);
this.params.video_url = videoUrl;
this.params.thumb_offset = thumbOffset;
this.params.share_to_feed = shareToFeed;
}

/**
* Sets the thumbnail offset time in the request.
*
* @param thumbOffset the thumbnail offset time.
*
* @returns this object, for chained invocation.
*/
public withThumbOffset(thumbOffset: number): this {
this.params.thumb_offset = thumbOffset;
return this;
}

/**
* Sets whether the reel should be shared in the feed as well.
*
* @param shareToFeed whether the reel should be shared in the feed as well.
*
* @returns this object, for chained invocation.
*/
public withShareToFeed(shareToFeed: boolean): this {
this.params.share_to_feed = shareToFeed;
return this;
}

/**
* @inheritdoc
*/
protected mediaType(): MediaType | undefined {
return MediaType.REEL;
}
}
33 changes: 33 additions & 0 deletions src/test/Client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { GetPageRecentlySearchedHashtagsRequest } from '../main/requests/page/re
import { GetPageStoriesRequest } from '../main/requests/page/stories/GetPageStoriesRequest';
import { GetTagsRequest } from '../main/requests/page/tags/GetTagsRequest';
import { TestConstants } from './TestConstants';
import { PostPageReelMediaRequest } from '../main/requests/page/media/PostPageReelMediaRequest';

describe('Client', () => {
const client: Client = new Client(TestConstants.ACCESS_TOKEN, TestConstants.PAGE_ID);
Expand Down Expand Up @@ -527,6 +528,38 @@ describe('Client', () => {
);
});

it('Builds a PostPageReelMediaRequest', () => {
expect(client.newPostPageReelMediaRequest(TestConstants.MEDIA_URL)).toEqual(
new PostPageReelMediaRequest(TestConstants.ACCESS_TOKEN, TestConstants.PAGE_ID, TestConstants.MEDIA_URL)
);
expect(clientExplicitVersion.newPostPageReelMediaRequest(TestConstants.MEDIA_URL)).toEqual(
new PostPageReelMediaRequest(
TestConstants.ACCESS_TOKEN,
TestConstants.PAGE_ID,
TestConstants.MEDIA_URL
).withApiVersion(TestConstants.API_VERSION)
);
expect(
client.newPostPageReelMediaRequest(
TestConstants.MEDIA_URL,
TestConstants.CAPTION,
TestConstants.THUMB_OFFSET,
TestConstants.SHARE_TO_FEED,
TestConstants.LOCATION_ID
)
).toEqual(
new PostPageReelMediaRequest(
TestConstants.ACCESS_TOKEN,
TestConstants.PAGE_ID,
TestConstants.MEDIA_URL,
TestConstants.CAPTION,
TestConstants.THUMB_OFFSET,
TestConstants.SHARE_TO_FEED,
TestConstants.LOCATION_ID
)
);
});

it('Builds a PostPageCarouselMediaRequest', () => {
expect(client.newPostPageCarouselMediaRequest(TestConstants.ID_ARRAY)).toEqual(
new PostPageCarouselMediaRequest(TestConstants.ACCESS_TOKEN, TestConstants.PAGE_ID, TestConstants.ID_ARRAY)
Expand Down
5 changes: 5 additions & 0 deletions src/test/TestConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,11 @@ export class TestConstants {
*/
static readonly CAPTION: string = 'Test Caption';

/**
* A dummy share_to_feed.
*/
static readonly SHARE_TO_FEED: boolean = true;

/**
* A dummy location id.
*/
Expand Down
60 changes: 60 additions & 0 deletions src/test/requests/page/media/PostPageReelMediaRequest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { Constants } from '../../../../main/Constants';
import { CreatedObjectIdResponse } from '../../../../main/requests/common/CreatedObjectIdResponse';
import { PostPageReelMediaRequest } from '../../../../main/requests/page/media/PostPageReelMediaRequest';
import { TestConstants } from '../../../TestConstants';

describe('PostPageReelMediaRequest.', () => {
const request: PostPageReelMediaRequest = new PostPageReelMediaRequest(
TestConstants.ACCESS_TOKEN,
TestConstants.PAGE_ID,
TestConstants.MEDIA_URL,
TestConstants.CAPTION,
TestConstants.THUMB_OFFSET,
TestConstants.SHARE_TO_FEED,
TestConstants.LOCATION_ID
);
const requestBare: PostPageReelMediaRequest = new PostPageReelMediaRequest(
TestConstants.ACCESS_TOKEN,
TestConstants.PAGE_ID,
TestConstants.MEDIA_URL
);

it('Builds the config', () => {
expect(request.config().method).toEqual('POST');
expect(request.config().url).toEqual(`/${TestConstants.PAGE_ID}/media`);
expect(request.config().params.video_url).toEqual(TestConstants.MEDIA_URL);
expect(request.config().params.caption).toEqual(TestConstants.CAPTION);
expect(request.config().params.thumb_offset).toEqual(TestConstants.THUMB_OFFSET);
expect(request.config().params.share_to_feed).toEqual(TestConstants.SHARE_TO_FEED);
expect(request.config().params.location_id).toEqual(TestConstants.LOCATION_ID);
});

it('Adds params request config', () => {
expect(requestBare.config().params.video_url).toEqual(TestConstants.MEDIA_URL);
expect(requestBare.config().params.caption).toBeUndefined();
expect(requestBare.config().params.location_id).toBeUndefined();
expect(requestBare.config().params.share_to_feed).toBeUndefined();
expect(requestBare.config().params.thumb_offset).toBeUndefined();

requestBare.withCaption(TestConstants.CAPTION);
requestBare.withLocationId(TestConstants.LOCATION_ID);
requestBare.withThumbOffset(TestConstants.THUMB_OFFSET);
requestBare.withShareToFeed(TestConstants.SHARE_TO_FEED);

expect(requestBare.config().params.caption).toEqual(TestConstants.CAPTION);
expect(requestBare.config().params.thumb_offset).toEqual(TestConstants.THUMB_OFFSET);
expect(requestBare.config().params.location_id).toEqual(TestConstants.LOCATION_ID);
expect(request.config().params.share_to_feed).toEqual(TestConstants.SHARE_TO_FEED);
});

const mock = new MockAdapter(axios);
mock.onPost(`${Constants.API_URL}/${TestConstants.PAGE_ID}/media`).reply(200, { id: TestConstants.CONTAINER_ID });
it('Parses the response', () => {
expect.assertions(1);
return request.execute().then((response) => {
expect(response).toEqual(new CreatedObjectIdResponse({ id: TestConstants.CONTAINER_ID }));
});
});
});

0 comments on commit 0c92c5e

Please sign in to comment.