@@ -20,10 +20,11 @@ import type {
2020 UserV2MuteResult ,
2121 UserV2UnfollowResult ,
2222 TweetV2BookmarkResult ,
23+ EUploadMimeType ,
2324} from '../types' ;
2425import TwitterApiv2LabsReadWrite from '../v2-labs/client.v2.labs.write' ;
2526import { 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