Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
45 changes: 42 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ x-api-sdk-ts is a flexible TypeScript SDK for the X API, providing a type-safe,
- [Check for successful response](#check-for-successful-response)
- [Usage example](#usage-example)
- [Media Upload](#media-upload)
- [Reduce your API usage](#reduce-your-api-usage)
- [Upload with custom chunk size](#upload-with-custom-chunk-size)
- [Upload with custom minimum waiting time](#upload-with-custom-minimum-waiting-time)
- [Get Media Upload Status](#get-media-upload-status)
- [Add Metadata to Media](#add-metadata-to-media)
- [Create Post](#create-post)
Expand Down Expand Up @@ -170,11 +173,9 @@ if (twitterClient.isErrorResponse(postResponse)) {

### Media Upload

The `upload` method automatically handles the chunked upload process.
The `upload` method automatically handles the chunked upload process. (Read more about it in [X API Documentation](https://docs.x.com/en/x-api/media/quickstart/media-upload-chunked))
The file is uploaded in chunks of MIN(4MB, MAX(1MB, media.length / 10)).

**NB:** You can also specify a custom chunk size as 4th parameter which can be bigger than 4MB. Note that the API has a maximum chunk size of 4MB (for free api access).

```typescript
const mediaResponse = await twitterClient.media.upload(
fs.readFileSync('path/to/media/doge.jpeg'),
Expand All @@ -188,6 +189,44 @@ const mediaData = mediaResponse.data;
const mediaId = mediaData.data.id;
```

#### Reduce your API usage

You can reduce your API usage by specifying a custom chunk size and a custom minimum waiting time.

##### Upload with custom chunk size

You can specify a custom chunk size as 5th parameter which can be bigger than 4MB.
Note that the API has a maximum chunk size of 4MB (confirmed for free api access).

```typescript
const mediaResponse = await twitterClient.media.upload(
fs.readFileSync('path/to/media/doge.jpeg'),
'image/jpeg',
'tweet_image',
null,
1024 * 1024 * 3 // 3MB
);
```

##### Upload with custom minimum waiting time

You can specify a custom minimum waiting time as 6th parameter.

The default minimum waiting time is 1 second and X provides a recommended minimum waiting time.
However, on a Free API access, it is recommended to wait as long as it makes sense for your use case, in order to reduce API Usage.
Read more about it in this issue: [#7](https://github.com/Micka33/x-api-sdk-ts/issues/7).

```typescript
const mediaResponse = await twitterClient.media.upload(
fs.readFileSync('path/to/media/doge.jpeg'),
'image/jpeg',
'tweet_image',
null,
null,
60 // 60 seconds
);
```

### Get Media Upload Status

```typescript
Expand Down
10 changes: 6 additions & 4 deletions src/api/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export class Media extends AbstractMedia {
mimeType: string,
category: 'amplify_video' | 'tweet_gif' | 'tweet_image' | 'tweet_video' | 'dm_video' | 'subtitles',
additionalOwners: string[] | null = null,
chunkSize?: number,
chunkSize?: number | null,
minWaitingTimeInSeconds?: number
): Promise<RCResponse<IUploadMediaResponse>> {
// Step 1: INIT - Initialize the upload
const initResponse = await this.initMediaUpload(media.length, mimeType, category, additionalOwners);
Expand Down Expand Up @@ -60,7 +61,7 @@ export class Media extends AbstractMedia {
// Step 4: Check if processing is needed
if (finalizeResponse.data.data.processing_info) {
// If processing is needed, wait for it to complete
const waitingResponse = await this.waitForProcessing(mediaId, finalizeResponse);
const waitingResponse = await this.waitForProcessing(mediaId, finalizeResponse, minWaitingTimeInSeconds);
return waitingResponse;
}
return finalizeResponse;
Expand Down Expand Up @@ -277,7 +278,8 @@ export class Media extends AbstractMedia {
*/
private async waitForProcessing(
mediaId: string,
initialResponse: RCResponseSimple<IUploadMediaResponse>
initialResponse: RCResponseSimple<IUploadMediaResponse>,
minWaitingTimeInSeconds?: number
): Promise<RCResponse<IUploadMediaResponse>> {
let response: RCResponseSimple<IUploadMediaResponse> = initialResponse;
let data = response.data.data;
Expand All @@ -289,7 +291,7 @@ export class Media extends AbstractMedia {
['pending', 'in_progress'].includes(data.processing_info.state)
) {
// Wait for the recommended time before checking again
const checkAfterSecs = data.processing_info?.check_after_secs || 1;
const checkAfterSecs = Math.min(minWaitingTimeInSeconds || 0, data.processing_info?.check_after_secs || 1);
await new Promise(resolve => setTimeout(resolve, checkAfterSecs * 1000));

// Check status
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/api/IMedia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export abstract class AbstractMedia extends AbstractApi {
mimeType: string,
category: 'amplify_video' | 'tweet_gif' | 'tweet_image' | 'tweet_video' | 'dm_video' | 'subtitles',
additionalOwners?: string[] | null,
chunkSize?: number
chunkSize?: number | null,
minWaitingTimeInSeconds?: number
): Promise<RCResponse<IUploadMediaResponse>>;

/**
Expand Down