@@ -31,11 +31,31 @@ const defaults = {
3131 bearer_token : null
3232} ;
3333
34+ // Twitter expects POST body parameters to be URL-encoded: https://developer.twitter.com/en/docs/basics/authentication/guides/creating-a-signature
35+ // However, some endpoints expect a JSON payload - https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/new-event
36+ // It appears that JSON payloads don't need to be included in the signature,
37+ // because sending DMs works without signing the POST body
38+ const JSON_ENDPOINTS = [
39+ "direct_messages/events/new" ,
40+ "direct_messages/welcome_messages/new" ,
41+ "direct_messages/welcome_messages/rules/new"
42+ ] ;
43+
3444const baseHeaders = {
3545 "Content-Type" : "application/json" ,
3646 Accept : "application/json"
3747} ;
3848
49+ function percentEncode ( string ) {
50+ // From OAuth.prototype.percentEncode
51+ return string
52+ . replace ( / ! / g, "%21" )
53+ . replace ( / \* / g, "%2A" )
54+ . replace ( / ' / g, "%27" )
55+ . replace ( / \( / g, "%28" )
56+ . replace ( / \) / g, "%29" ) ;
57+ }
58+
3959class Twitter {
4060 constructor ( options ) {
4161 const config = Object . assign ( { } , defaults , options ) ;
@@ -64,7 +84,7 @@ class Twitter {
6484 static _handleResponse ( response ) {
6585 const headers = response . headers . raw ( ) ; // https://github.com/bitinn/node-fetch/issues/495
6686 return response . json ( ) . then ( res => {
67- res . _headers = headers ;
87+ res . _headers = headers ; // TODO: this creates an array-like object when it adds _headers to an array response
6888 return res ;
6989 } ) ;
7090 }
@@ -151,7 +171,9 @@ class Twitter {
151171 url : `${ this . url } /${ resource } .json` ,
152172 method
153173 } ;
154- if ( parameters ) requestData . url += "?" + querystring . stringify ( parameters ) ;
174+ if ( parameters )
175+ if ( method === "POST" ) requestData . data = parameters ;
176+ else requestData . url += "?" + querystring . stringify ( parameters ) ;
155177
156178 let headers = { } ;
157179 if ( this . authType === "User" ) {
@@ -178,8 +200,8 @@ class Twitter {
178200
179201 return Fetch ( requestData . url , { headers } )
180202 . then ( Twitter . _handleResponse )
181- . then (
182- results => ( "errors" in results ? Promise . reject ( results ) : results )
203+ . then ( results =>
204+ "errors" in results ? Promise . reject ( results ) : results
183205 ) ;
184206 }
185207
@@ -190,14 +212,22 @@ class Twitter {
190212 parameters
191213 ) ;
192214
215+ const postHeaders = Object . assign ( { } , baseHeaders , headers ) ;
216+ if ( JSON_ENDPOINTS . includes ( resource ) ) {
217+ body = JSON . stringify ( body ) ;
218+ } else {
219+ body = querystring . stringify ( parameters ) ;
220+ postHeaders [ "Content-Type" ] = "application/x-www-form-urlencoded" ;
221+ }
222+
193223 return Fetch ( requestData . url , {
194224 method : "POST" ,
195- headers : Object . assign ( { } , baseHeaders , headers ) ,
196- body : JSON . stringify ( body )
225+ headers : postHeaders ,
226+ body : percentEncode ( body )
197227 } )
198228 . then ( Twitter . _handleResponse )
199- . then (
200- results => ( "errors" in results ? Promise . reject ( results ) : results )
229+ . then ( results =>
230+ "errors" in results ? Promise . reject ( results ) : results
201231 ) ;
202232 }
203233
@@ -207,17 +237,26 @@ class Twitter {
207237
208238 const stream = new Stream ( ) ;
209239
240+ // POST the request, in order to accommodate long parameter lists, e.g.
241+ // up to 5000 ids for statuses/filter - https://developer.twitter.com/en/docs/tweets/filter-realtime/api-reference/post-statuses-filter
210242 const requestData = {
211243 url : `${ getUrl ( "stream" ) } /${ resource } .json` ,
212- method : "GET "
244+ method : "POST "
213245 } ;
214- if ( parameters ) requestData . url += "?" + querystring . stringify ( parameters ) ;
246+ if ( parameters ) requestData . data = parameters ;
215247
216248 const headers = this . client . toHeader (
217249 this . client . authorize ( requestData , this . token )
218250 ) ;
219251
220- const request = Fetch ( requestData . url , { headers } ) ;
252+ const request = Fetch ( requestData . url , {
253+ method : "POST" ,
254+ headers : {
255+ ...headers ,
256+ "Content-Type" : "application/x-www-form-urlencoded"
257+ } ,
258+ body : percentEncode ( querystring . stringify ( parameters ) )
259+ } ) ;
221260
222261 request
223262 . then ( response => {
0 commit comments