@@ -14,17 +14,7 @@ import { DefaultFlag } from '@/models';
1414import { UsersApiUtils } from './apis/usersApi' ;
1515
1616export type TwitterOpenApiParams = {
17- lang ?: string ;
1817 fetchApi ?: i . FetchAPI ;
19-
20- flag ?: DefaultFlag ;
21- accessToken ?: string ;
22- userAgent ?: string ;
23- } ;
24-
25- export type TwitterOpenApiCookie = {
26- name : string ;
27- value : string ;
2818} ;
2919
3020export class TwitterOpenApi {
@@ -36,6 +26,28 @@ export class TwitterOpenApi {
3626 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36' ;
3727 static bearer =
3828 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA' ;
29+
30+ static browser_headers = {
31+ accept : 'text/plain, */*; q=0.01' ,
32+ 'accept-encoding' : 'gzip, deflate, br' ,
33+ 'accept-language' : 'en-US,en;q=0.9' ,
34+ 'cache-control' : 'no-cache' ,
35+ pragma : 'no-cache' ,
36+ 'sec-ch-ua' : '"Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"' ,
37+ 'sec-ch-ua-mobile' : '?0' ,
38+ 'sec-ch-ua-platform' : '"Windows"' ,
39+ 'sec-fetch-dest' : 'empty' ,
40+ 'sec-fetch-mode' : 'cors' ,
41+ 'sec-fetch-site' : 'same-site' ,
42+ 'user-agent' : TwitterOpenApi . userAgent ,
43+ } ;
44+
45+ static api_key = {
46+ 'x-twitter-client-language' : 'en' ,
47+ 'x-twitter-active-user' : 'yes' ,
48+ ...TwitterOpenApi . browser_headers ,
49+ } ;
50+
3951 param : TwitterOpenApiParams ;
4052
4153 constructor ( param : TwitterOpenApiParams = { } ) {
@@ -46,103 +58,92 @@ export class TwitterOpenApi {
4658 return this . param . fetchApi || fetch ;
4759 }
4860
49- cookieDecode ( cookie : string ) : TwitterOpenApiCookie [ ] {
50- return cookie
51- . split ( ', ' )
52- . map ( ( cookie ) => cookie . split ( ';' ) [ 0 ] )
53- . filter ( ( cookie ) => cookie . indexOf ( '=' ) != - 1 )
54- . map ( ( cookie ) => cookie . split ( '=' ) )
55- . map ( ( [ name , value ] ) => ( {
56- name : name ,
57- value : value ,
58- } ) ) ;
61+ cookie_normalize ( cookie : string [ ] ) : { [ key : string ] : string } {
62+ return cookie . reduce ( ( a , b ) => {
63+ const [ key , value ] = b . split ( '=' ) ;
64+ return { ...a , [ key ] : value } ;
65+ } , { } ) ;
66+ }
67+
68+ cookieEncode ( cookie : { [ key : string ] : string } ) : string {
69+ return Object . entries ( cookie )
70+ . map ( ( [ key , value ] ) => `${ key } =${ value } ` )
71+ . join ( '; ' ) ;
5972 }
60- cookieEncode ( cookie : TwitterOpenApiCookie [ ] ) : string {
61- return cookie . map ( ( cookie ) => `${ cookie . name } =${ cookie . value } ` ) . join ( '; ' ) ;
73+
74+ setCookies ( context : i . RequestContext , cookies : { [ key : string ] : string } ) : i . RequestContext {
75+ if ( context . init . headers ) {
76+ const headers = context . init . headers as i . HTTPHeaders ;
77+ headers [ 'cookie' ] = this . cookieEncode ( cookies ) ;
78+ }
79+ return context ;
6280 }
6381
64- async getGuestSession ( ) : Promise < TwitterOpenApiCookie [ ] > {
65- const response = await this . fetchApi ( TwitterOpenApi . twitter , { method : 'GET' , redirect : 'manual' } ) ;
66- const chars = [ '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' ] ;
67- const csrfToken = [ ...Array ( 32 ) ] . reduce ( ( a ) => a + chars [ Math . floor ( Math . random ( ) * chars . length ) ] , '' ) ;
68- const cookies = this . cookieDecode ( response . headers . get ( 'set-cookie' ) ! ) ;
69- cookies . push ( { name : 'ct0' , value : csrfToken } ) ;
82+ async getGuestClient ( ) : Promise < TwitterOpenApiClient > {
83+ let cookies : { [ key : string ] : string } = { } ;
84+
85+ const response = await this . fetchApi ( TwitterOpenApi . twitter , {
86+ method : 'GET' ,
87+ redirect : 'manual' ,
88+ headers : { Cookie : this . cookieEncode ( cookies ) } ,
89+ } ) ;
90+ cookies = { ...cookies , ...this . cookie_normalize ( response . headers . get ( 'set-cookie' ) ! . split ( ', ' ) ) } ;
7091
7192 const html = await this . fetchApi ( TwitterOpenApi . twitter , {
7293 method : 'GET' ,
7394 headers : { Cookie : this . cookieEncode ( cookies ) } ,
7495 } ) . then ( ( response ) => response . text ( ) ) ;
7596
76- const re = new RegExp ( '<script nonce="([a-zA-Z0-9]{48})">(document.cookie="(.*?)";)+<\\/script>' ) ;
97+ const re = new RegExp ( 'document.cookie="(.*?)";' , 'g' ) ;
98+
99+ const find = [ ...html . matchAll ( re ) ] . map ( ( e ) => e [ 1 ] ) ;
100+ cookies = { ...cookies , ...this . cookie_normalize ( find ) } ;
101+
102+ cookies = Object . entries ( cookies )
103+ . filter ( ( [ key , value ] ) => key != 'ct0' )
104+ . reduce ( ( a , [ key , value ] ) => ( { ...a , [ key ] : value } ) , { } ) ;
105+
106+ if ( ! cookies [ 'gt' ] ) {
107+ const activate_headers = {
108+ ...TwitterOpenApi . api_key ,
109+ authorization : `Bearer ${ TwitterOpenApi . bearer } ` ,
110+ } ;
111+ const { guest_token } = await this . fetchApi ( 'https://api.twitter.com/1.1/guest/activate.json' , {
112+ method : 'POST' ,
113+ headers : activate_headers ,
114+ } ) . then ( ( response ) => response . json ( ) ) ;
115+ cookies [ 'gt' ] = guest_token ;
116+ }
77117
78- const script = html . match ( re ) ! [ 0 ] ;
79- script
80- . split ( 'document.cookie="' )
81- . slice ( 1 )
82- . map ( ( e ) => e . replace ( '</script>' , '' ) . replace ( '";' , '' ) )
83- . map ( ( e ) => this . cookieDecode ( e ) [ 0 ] )
84- . filter ( ( e ) => cookies . findIndex ( ( c ) => c . name == e . name ) == - 1 )
85- . forEach ( ( e ) => cookies . push ( e ) ) ;
118+ // const chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
119+ // const csrfToken = [...Array(32)].reduce((a) => a + chars[Math.floor(Math.random() * chars.length)], '');
120+ // cookies.push({ name: 'ct0', value: csrfToken });
86121
87- return cookies ;
122+ return this . getClientFromCookies ( cookies ) ;
88123 }
89124
90- setCookies ( context : i . RequestContext , cookies : TwitterOpenApiCookie [ ] ) : i . RequestContext {
91- if ( context . init . headers ) {
92- const headers = context . init . headers as i . HTTPHeaders ;
93- headers [ 'cookie' ] = this . cookieEncode ( cookies ) ;
125+ async getClientFromCookies ( cookies : { [ key : string ] : string } ) : Promise < TwitterOpenApiClient > {
126+ let api_key : { [ key : string ] : string } = { ...TwitterOpenApi . api_key } ;
127+ if ( cookies [ 'ct0' ] ) {
128+ api_key [ 'x-twitter-auth-type' ] = 'OAuth2Session' ;
129+ api_key [ 'x-csrf-token' ] = cookies [ 'ct0' ] ;
130+ }
131+ if ( cookies [ 'gt' ] ) {
132+ api_key [ 'x-guest-token' ] = cookies [ 'gt' ] ;
94133 }
95- return context ;
96- }
97134
98- async getClient ( ) : Promise < TwitterOpenApiClient > {
99- const cookies : TwitterOpenApiCookie [ ] = await this . getGuestSession ( ) ;
100135 const config : i . ConfigurationParameters = {
101136 fetchApi : this . fetchApi ,
102137 middleware : [ { pre : async ( context ) => this . setCookies ( context , cookies ) } ] ,
103- apiKey : ( key ) => {
104- return {
105- 'user-agent' : this . param . userAgent || TwitterOpenApi . userAgent ,
106- 'x-twitter-client-language' : this . param . lang ?? 'en' ,
107- 'x-twitter-active-user' : 'yes' ,
108- 'x-csrf-token' : cookies . find ( ( cookie ) => cookie . name === 'ct0' ) ?. value ,
109- 'x-guest-token' : cookies . find ( ( cookie ) => cookie . name === 'gt' ) ?. value ,
110- } [ key ] ! ;
111- } ,
112- accessToken : this . param . accessToken || TwitterOpenApi . bearer ,
138+ apiKey : ( key ) => api_key [ key . toLowerCase ( ) ] ,
139+ accessToken : TwitterOpenApi . bearer ,
113140 } ;
114- const flag =
115- this . param . flag ||
116- ( ( await this . fetchApi ( TwitterOpenApi . url , { method : 'GET' } ) . then ( ( res ) => res . json ( ) ) ) as DefaultFlag ) ;
117- return new TwitterOpenApiClient ( new i . Configuration ( config ) , flag ) ;
141+ return this . getClient ( new i . Configuration ( config ) ) ;
118142 }
119143
120- async getClientFromCookies ( ct0 : string , authToken : string ) : Promise < TwitterOpenApiClient > {
121- const guestCookies : TwitterOpenApiCookie [ ] = await this . getGuestSession ( ) ;
122- const cookies = [
123- { name : 'auth_token' , value : authToken } ,
124- { name : 'ct0' , value : ct0 } ,
125- ] ;
126- guestCookies . filter ( ( e ) => cookies . findIndex ( ( c ) => c . name == e . name ) == - 1 ) . forEach ( ( e ) => cookies . push ( e ) ) ;
127-
128- const config : i . ConfigurationParameters = {
129- fetchApi : this . param . fetchApi || fetch ,
130- middleware : [ { pre : async ( context ) => this . setCookies ( context , cookies ) } ] ,
131- apiKey : ( key ) => {
132- return {
133- 'user-agent' : this . param . userAgent || TwitterOpenApi . userAgent ,
134- 'x-twitter-client-language' : this . param . lang ?? 'en' ,
135- 'x-twitter-active-user' : 'yes' ,
136- 'x-twitter-auth-type' : 'OAuth2Session' ,
137- 'x-csrf-token' : cookies . find ( ( cookie ) => cookie . name === 'ct0' ) ?. value ,
138- } [ key ] ! ;
139- } ,
140- accessToken : this . param . accessToken || TwitterOpenApi . bearer ,
141- } ;
142- const flag =
143- this . param . flag ||
144- ( ( await this . fetchApi ( TwitterOpenApi . url , { method : 'GET' } ) . then ( ( res ) => res . json ( ) ) ) as DefaultFlag ) ;
145- return new TwitterOpenApiClient ( new i . Configuration ( config ) , flag ) ;
144+ async getClient ( api : i . Configuration ) : Promise < TwitterOpenApiClient > {
145+ const flag = ( await this . fetchApi ( TwitterOpenApi . url , { method : 'GET' } ) . then ( ( res ) => res . json ( ) ) ) as DefaultFlag ;
146+ return new TwitterOpenApiClient ( api , flag ) ;
146147 }
147148}
148149
0 commit comments