Skip to content

Commit 7c9c316

Browse files
authored
fix(upload-media): use form-data headers (#570)
* fix(upload-media): use form-data headers, better typing * chore(console.log): remove log
1 parent 695b434 commit 7c9c316

File tree

2 files changed

+27
-11
lines changed

2 files changed

+27
-11
lines changed

src/types/v2/media.v2.types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1+
export type MediaV2MediaCategory = 'tweet_image' | 'tweet_video' | 'tweet_gif' | 'dm_image' | 'dm_video' | 'dm_gif' | 'subtitles';
2+
13
export interface MediaV2UploadInitParams {
24
command: 'INIT';
35
media_type: string;
46
total_bytes: number;
5-
media_category?: string;
7+
media_category?: MediaV2MediaCategory;
68
}
79

810
export interface MediaV2UploadAppendParams {
911
command: 'APPEND';
1012
media_id: string;
1113
segment_index: number;
12-
media: Buffer | string;
14+
media: Buffer;
1315
}
1416

1517
export interface MediaV2UploadFinalizeParams {

src/v2/client.v2.write.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ import type {
2020
UserV2MuteResult,
2121
UserV2UnfollowResult,
2222
TweetV2BookmarkResult,
23+
EUploadMimeType,
2324
} from '../types';
2425
import TwitterApiv2LabsReadWrite from '../v2-labs/client.v2.labs.write';
2526
import { CreateDMConversationParams, PostDMInConversationParams, PostDMInConversationResult } from '../types/v2/dm.v2.types';
26-
import { MediaV2UploadAppendParams, MediaV2UploadFinalizeParams, MediaV2UploadInitParams, MediaV2UploadResponse } from '../types/v2/media.v2.types';
27+
import { MediaV2MediaCategory, MediaV2UploadAppendParams, MediaV2UploadFinalizeParams, MediaV2UploadInitParams, MediaV2UploadResponse } from '../types/v2/media.v2.types';
2728

2829
/**
2930
* Base Twitter v2 client with read/write rights.
@@ -133,24 +134,37 @@ export default class TwitterApiv2ReadWrite extends TwitterApiv2ReadOnly {
133134
*/
134135
public async uploadMedia(
135136
media: Buffer,
136-
options: { media_type: string; media_category?: string },
137+
options: { media_type: EUploadMimeType; media_category?: MediaV2MediaCategory },
137138
chunkSize: number = 1024 * 1024
138139
): Promise<string> {
140+
let media_category = options.media_category;
141+
// If no media category is provided, try to infer it from the media type
142+
if (!options.media_category) {
143+
if (options.media_type.includes('gif')) {
144+
media_category = 'tweet_gif';
145+
} else if (options.media_type.includes('image')) {
146+
media_category = 'tweet_image';
147+
} else if (options.media_type.includes('video')) {
148+
media_category = 'tweet_video';
149+
}
150+
}
151+
139152
const initArguments: MediaV2UploadInitParams = {
140153
command: 'INIT',
141154
media_type: options.media_type,
142155
total_bytes: media.length,
143-
media_category: options.media_category,
156+
media_category,
144157
};
145158

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

149-
const chunks = Math.ceil(media.length / chunkSize);
150-
for (let i = 0; i < chunks; i++) {
162+
const chunksCount = Math.ceil(media.length / chunkSize);
163+
const mediaArray = new Uint8Array(media);
164+
for (let i = 0; i < chunksCount; i++) {
151165
const start = i * chunkSize;
152166
const end = Math.min(start + chunkSize, media.length);
153-
const mediaChunk = Uint8Array.prototype.slice.call(media, start, end);
167+
const mediaChunk = mediaArray.slice(start, end);
154168
const chunkedBuffer = Buffer.from(mediaChunk);
155169

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

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

166180
const finalizeArguments: MediaV2UploadFinalizeParams = {
167181
command: 'FINALIZE',
168182
media_id: mediaId,
169183
};
170184

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

0 commit comments

Comments
 (0)