Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: PLhery/node-twitter-api-v2
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 1.20.1
Choose a base ref
...
head repository: PLhery/node-twitter-api-v2
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 1.20.2
Choose a head ref
  • 3 commits
  • 5 files changed
  • 2 contributors

Commits on Feb 24, 2025

  1. fix(upload-media): use form-data headers (#570)

    * fix(upload-media): use form-data headers, better typing
    
    * chore(console.log): remove log
    leeran7 authored Feb 24, 2025
    Copy the full SHA
    7c9c316 View commit details
  2. fix(upload-media): adjust input type for media_type (#571)

    leeran7 authored Feb 24, 2025
    Copy the full SHA
    edad955 View commit details

Commits on Feb 25, 2025

  1. upgrade to 1.20.2

    PLhery committed Feb 25, 2025

    Unverified

    The email in this signature doesn’t match the committer email.
    Copy the full SHA
    54e1ffe View commit details
Showing with 34 additions and 14 deletions.
  1. +4 −0 changelog.md
  2. +2 −2 package-lock.json
  3. +1 −1 package.json
  4. +4 −2 src/types/v2/media.v2.types.ts
  5. +23 −9 src/v2/client.v2.write.ts
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
1.20.2
------
- fix: upload-media v2 endpoint #570 #571 (@leeran7)

1.20.1
------
- fix: absence of profile_banner_url field #565 (@MysticalHeat)
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "twitter-api-v2",
"version": "1.20.1",
"version": "1.20.2",
"description": "Strongly typed, full-featured, light, versatile yet powerful Twitter API v1.1 and v2 client for Node.js.",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
6 changes: 4 additions & 2 deletions src/types/v2/media.v2.types.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
export type MediaV2MediaCategory = 'tweet_image' | 'tweet_video' | 'tweet_gif' | 'dm_image' | 'dm_video' | 'dm_gif' | 'subtitles';

export interface MediaV2UploadInitParams {
command: 'INIT';
media_type: string;
total_bytes: number;
media_category?: string;
media_category?: MediaV2MediaCategory;
}

export interface MediaV2UploadAppendParams {
command: 'APPEND';
media_id: string;
segment_index: number;
media: Buffer | string;
media: Buffer;
}

export interface MediaV2UploadFinalizeParams {
32 changes: 23 additions & 9 deletions src/v2/client.v2.write.ts
Original file line number Diff line number Diff line change
@@ -20,10 +20,11 @@ import type {
UserV2MuteResult,
UserV2UnfollowResult,
TweetV2BookmarkResult,
EUploadMimeType,
} from '../types';
import TwitterApiv2LabsReadWrite from '../v2-labs/client.v2.labs.write';
import { CreateDMConversationParams, PostDMInConversationParams, PostDMInConversationResult } from '../types/v2/dm.v2.types';
import { MediaV2UploadAppendParams, MediaV2UploadFinalizeParams, MediaV2UploadInitParams, MediaV2UploadResponse } from '../types/v2/media.v2.types';
import { MediaV2MediaCategory, MediaV2UploadAppendParams, MediaV2UploadFinalizeParams, MediaV2UploadInitParams, MediaV2UploadResponse } from '../types/v2/media.v2.types';

/**
* Base Twitter v2 client with read/write rights.
@@ -133,24 +134,37 @@ export default class TwitterApiv2ReadWrite extends TwitterApiv2ReadOnly {
*/
public async uploadMedia(
media: Buffer,
options: { media_type: string; media_category?: string },
options: { media_type: `${EUploadMimeType}` | EUploadMimeType; media_category?: MediaV2MediaCategory },
chunkSize: number = 1024 * 1024
): Promise<string> {
let media_category = options.media_category;
// If no media category is provided, try to infer it from the media type
if (!options.media_category) {
if (options.media_type.includes('gif')) {
media_category = 'tweet_gif';
} else if (options.media_type.includes('image')) {
media_category = 'tweet_image';
} else if (options.media_type.includes('video')) {
media_category = 'tweet_video';
}
}

const initArguments: MediaV2UploadInitParams = {
command: 'INIT',
media_type: options.media_type,
total_bytes: media.length,
media_category: options.media_category,
media_category,
};

const initResponse = await this.post<MediaV2UploadResponse>('media/upload', initArguments);
const initResponse = await this.post<MediaV2UploadResponse>('media/upload', initArguments, { forceBodyMode: 'form-data' });
const mediaId = initResponse.data.id;

const chunks = Math.ceil(media.length / chunkSize);
for (let i = 0; i < chunks; i++) {
const chunksCount = Math.ceil(media.length / chunkSize);
const mediaArray = new Uint8Array(media);
for (let i = 0; i < chunksCount; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, media.length);
const mediaChunk = Uint8Array.prototype.slice.call(media, start, end);
const mediaChunk = mediaArray.slice(start, end);
const chunkedBuffer = Buffer.from(mediaChunk);

const appendArguments: MediaV2UploadAppendParams = {
@@ -160,15 +174,15 @@ export default class TwitterApiv2ReadWrite extends TwitterApiv2ReadOnly {
media: chunkedBuffer,
};

await this.post('media/upload', appendArguments);
await this.post('media/upload', appendArguments, { forceBodyMode: 'form-data' });
}

const finalizeArguments: MediaV2UploadFinalizeParams = {
command: 'FINALIZE',
media_id: mediaId,
};

const finalizeResponse = await this.post<MediaV2UploadResponse>('media/upload', finalizeArguments);
const finalizeResponse = await this.post<MediaV2UploadResponse>('media/upload', finalizeArguments, { forceBodyMode: 'form-data' });
if (finalizeResponse.data.processing_info) {
await this.waitForMediaProcessing(mediaId);
}