create_session_curl.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. #!/usr/bin/env python3
  2. """
  3. Requirements:
  4. pip install curl_cffi pyotp
  5. Usage:
  6. python3 tools/create_session_curl.py <username> <password> [totp_seed] [--append sessions.jsonl]
  7. Examples:
  8. # Output to terminal
  9. python3 tools/create_session_curl.py myusername mypassword TOTP_SECRET
  10. # Append to sessions.jsonl
  11. python3 tools/create_session_curl.py myusername mypassword TOTP_SECRET --append sessions.jsonl
  12. Output:
  13. {"kind": "cookie", "username": "...", "id": "...", "auth_token": "...", "ct0": "..."}
  14. """
  15. import sys
  16. import json
  17. import pyotp
  18. from curl_cffi import requests
  19. BEARER_TOKEN = "AAAAAAAAAAAAAAAAAAAAAFQODgEAAAAAVHTp76lzh3rFzcHbmHVvQxYYpTw%3DckAlMINMjmCwxUcaXbAN4XqJVdgMJaHqNOFgPMK0zN1qLqLQCF"
  20. BASE_URL = "https://api.x.com/1.1/onboarding/task.json"
  21. GUEST_ACTIVATE_URL = "https://api.x.com/1.1/guest/activate.json"
  22. # Subtask versions required by API
  23. SUBTASK_VERSIONS = {
  24. "action_list": 2, "alert_dialog": 1, "app_download_cta": 1,
  25. "check_logged_in_account": 2, "choice_selection": 3,
  26. "contacts_live_sync_permission_prompt": 0, "cta": 7, "email_verification": 2,
  27. "end_flow": 1, "enter_date": 1, "enter_email": 2, "enter_password": 5,
  28. "enter_phone": 2, "enter_recaptcha": 1, "enter_text": 5, "generic_urt": 3,
  29. "in_app_notification": 1, "interest_picker": 3, "js_instrumentation": 1,
  30. "menu_dialog": 1, "notifications_permission_prompt": 2, "open_account": 2,
  31. "open_home_timeline": 1, "open_link": 1, "phone_verification": 4,
  32. "privacy_options": 1, "security_key": 3, "select_avatar": 4,
  33. "select_banner": 2, "settings_list": 7, "show_code": 1, "sign_up": 2,
  34. "sign_up_review": 4, "tweet_selection_urt": 1, "update_users": 1,
  35. "upload_media": 1, "user_recommendations_list": 4,
  36. "user_recommendations_urt": 1, "wait_spinner": 3, "web_modal": 1
  37. }
  38. def get_base_headers(guest_token=None):
  39. """Build base headers for API requests."""
  40. headers = {
  41. "Authorization": f"Bearer {BEARER_TOKEN}",
  42. "Content-Type": "application/json",
  43. "Accept": "*/*",
  44. "Accept-Language": "en-US",
  45. "X-Twitter-Client-Language": "en-US",
  46. "Origin": "https://x.com",
  47. "Referer": "https://x.com/",
  48. }
  49. if guest_token:
  50. headers["X-Guest-Token"] = guest_token
  51. return headers
  52. def get_cookies_dict(session):
  53. """Extract cookies from session."""
  54. return session.cookies.get_dict() if hasattr(session.cookies, 'get_dict') else dict(session.cookies)
  55. def make_request(session, headers, flow_token, subtask_data, print_msg):
  56. """Generic request handler for flow steps."""
  57. print(f"[*] {print_msg}...", file=sys.stderr)
  58. payload = {
  59. "flow_token": flow_token,
  60. "subtask_inputs": [subtask_data] if isinstance(subtask_data, dict) else subtask_data
  61. }
  62. response = session.post(BASE_URL, json=payload, headers=headers)
  63. response.raise_for_status()
  64. data = response.json()
  65. new_flow_token = data.get('flow_token')
  66. if not new_flow_token:
  67. raise Exception(f"Failed to get flow token: {print_msg}")
  68. return new_flow_token, data
  69. def get_guest_token(session):
  70. """Get guest token for unauthenticated requests."""
  71. print("[*] Getting guest token...", file=sys.stderr)
  72. response = session.post(GUEST_ACTIVATE_URL, headers={"Authorization": f"Bearer {BEARER_TOKEN}"})
  73. response.raise_for_status()
  74. guest_token = response.json().get('guest_token')
  75. if not guest_token:
  76. raise Exception("Failed to obtain guest token")
  77. print(f"[*] Got guest token: {guest_token}", file=sys.stderr)
  78. return guest_token
  79. def init_flow(session, guest_token):
  80. """Initialize the login flow."""
  81. print("[*] Initializing login flow...", file=sys.stderr)
  82. headers = get_base_headers(guest_token)
  83. payload = {
  84. "input_flow_data": {
  85. "flow_context": {
  86. "debug_overrides": {},
  87. "start_location": {"location": "manual_link"}
  88. },
  89. "subtask_versions": SUBTASK_VERSIONS
  90. }
  91. }
  92. response = session.post(f"{BASE_URL}?flow_name=login", json=payload, headers=headers)
  93. response.raise_for_status()
  94. flow_token = response.json().get('flow_token')
  95. if not flow_token:
  96. raise Exception("Failed to get initial flow token")
  97. print("[*] Got initial flow token", file=sys.stderr)
  98. return flow_token, headers
  99. def submit_username(session, flow_token, headers, guest_token, username):
  100. """Submit username."""
  101. headers = headers.copy()
  102. headers["X-Guest-Token"] = guest_token
  103. subtask = {
  104. "subtask_id": "LoginEnterUserIdentifierSSO",
  105. "settings_list": {
  106. "setting_responses": [{
  107. "key": "user_identifier",
  108. "response_data": {"text_data": {"result": username}}
  109. }],
  110. "link": "next_link"
  111. }
  112. }
  113. flow_token, data = make_request(session, headers, flow_token, subtask, "Submitting username")
  114. # Check for denial (suspicious activity)
  115. if data.get('subtasks') and 'cta' in data['subtasks'][0]:
  116. error_msg = data['subtasks'][0]['cta'].get('primary_text', {}).get('text')
  117. if error_msg:
  118. raise Exception(f"Login denied: {error_msg}")
  119. return flow_token
  120. def submit_password(session, flow_token, headers, guest_token, password):
  121. """Submit password and detect if 2FA is needed."""
  122. headers = headers.copy()
  123. headers["X-Guest-Token"] = guest_token
  124. subtask = {
  125. "subtask_id": "LoginEnterPassword",
  126. "enter_password": {"password": password, "link": "next_link"}
  127. }
  128. flow_token, data = make_request(session, headers, flow_token, subtask, "Submitting password")
  129. needs_2fa = any(s.get('subtask_id') == 'LoginTwoFactorAuthChallenge' for s in data.get('subtasks', []))
  130. if needs_2fa:
  131. print("[*] 2FA required", file=sys.stderr)
  132. return flow_token, needs_2fa
  133. def submit_2fa(session, flow_token, headers, guest_token, totp_seed):
  134. """Submit 2FA code."""
  135. if not totp_seed:
  136. raise Exception("2FA required but no TOTP seed provided")
  137. code = pyotp.TOTP(totp_seed).now()
  138. print("[*] Generating 2FA code...", file=sys.stderr)
  139. headers = headers.copy()
  140. headers["X-Guest-Token"] = guest_token
  141. subtask = {
  142. "subtask_id": "LoginTwoFactorAuthChallenge",
  143. "enter_text": {"text": code, "link": "next_link"}
  144. }
  145. flow_token, _ = make_request(session, headers, flow_token, subtask, "Submitting 2FA code")
  146. return flow_token
  147. def submit_js_instrumentation(session, flow_token, headers, guest_token):
  148. """Submit JS instrumentation response."""
  149. headers = headers.copy()
  150. headers["X-Guest-Token"] = guest_token
  151. subtask = {
  152. "subtask_id": "LoginJsInstrumentationSubtask",
  153. "js_instrumentation": {
  154. "response": '{"rf":{"a4fc506d24bb4843c48a1966940c2796bf4fb7617a2d515ad3297b7df6b459b6":121,"bff66e16f1d7ea28c04653dc32479cf416a9c8b67c80cb8ad533b2a44fee82a3":-1,"ac4008077a7e6ca03210159dbe2134dea72a616f03832178314bb9931645e4f7":-22,"c3a8a81a9b2706c6fec42c771da65a9597c537b8e4d9b39e8e58de9fe31ff239":-12},"s":"ZHYaDA9iXRxOl2J3AZ9cc23iJx-Fg5E82KIBA_fgeZFugZGYzRtf8Bl3EUeeYgsK30gLFD2jTQx9fAMsnYCw0j8ahEy4Pb5siM5zD6n7YgOeWmFFaXoTwaGY4H0o-jQnZi5yWZRAnFi4lVuCVouNz_xd2BO2sobCO7QuyOsOxQn2CWx7bjD8vPAzT5BS1mICqUWyjZDjLnRZJU6cSQG5YFIHEPBa8Kj-v1JFgkdAfAMIdVvP7C80HWoOqYivQR7IBuOAI4xCeLQEdxlGeT-JYStlP9dcU5St7jI6ExyMeQnRicOcxXLXsan8i5Joautk2M8dAJFByzBaG4wtrPhQ3QAAAZEi-_t7"}',
  155. "link": "next_link"
  156. }
  157. }
  158. flow_token, _ = make_request(session, headers, flow_token, subtask, "Submitting JS instrumentation")
  159. return flow_token
  160. def complete_flow(session, flow_token, headers):
  161. """Complete the login flow."""
  162. cookies = get_cookies_dict(session)
  163. headers = headers.copy()
  164. headers["X-Twitter-Auth-Type"] = "OAuth2Session"
  165. if cookies.get('ct0'):
  166. headers["X-Csrf-Token"] = cookies['ct0']
  167. subtask = {
  168. "subtask_id": "AccountDuplicationCheck",
  169. "check_logged_in_account": {"link": "AccountDuplicationCheck_false"}
  170. }
  171. make_request(session, headers, flow_token, subtask, "Completing login flow")
  172. def extract_user_id(cookies_dict):
  173. """Extract user ID from twid cookie."""
  174. twid = cookies_dict.get('twid', '').strip('"')
  175. for prefix in ['u=', 'u%3D']:
  176. if prefix in twid:
  177. return twid.split(prefix)[1].split('&')[0].strip('"')
  178. return None
  179. def login_and_get_cookies(username, password, totp_seed=None):
  180. """Authenticate with X.com and extract session cookies."""
  181. session = requests.Session(impersonate="chrome")
  182. try:
  183. guest_token = get_guest_token(session)
  184. flow_token, headers = init_flow(session, guest_token)
  185. flow_token = submit_js_instrumentation(session, flow_token, headers, guest_token)
  186. flow_token = submit_username(session, flow_token, headers, guest_token, username)
  187. flow_token, needs_2fa = submit_password(session, flow_token, headers, guest_token, password)
  188. if needs_2fa:
  189. flow_token = submit_2fa(session, flow_token, headers, guest_token, totp_seed)
  190. complete_flow(session, flow_token, headers)
  191. cookies_dict = get_cookies_dict(session)
  192. cookies_dict['username'] = username
  193. user_id = extract_user_id(cookies_dict)
  194. if user_id:
  195. cookies_dict['id'] = user_id
  196. print("[*] Successfully authenticated", file=sys.stderr)
  197. return cookies_dict
  198. finally:
  199. session.close()
  200. def main():
  201. if len(sys.argv) < 3:
  202. print('Usage: python3 create_session_curl.py username password [totp_seed] [--append sessions.jsonl]', file=sys.stderr)
  203. sys.exit(1)
  204. username = sys.argv[1]
  205. password = sys.argv[2]
  206. totp_seed = None
  207. append_file = None
  208. # Parse optional arguments
  209. i = 3
  210. while i < len(sys.argv):
  211. arg = sys.argv[i]
  212. if arg == '--append':
  213. if i + 1 < len(sys.argv):
  214. append_file = sys.argv[i + 1]
  215. i += 2
  216. else:
  217. print('[!] Error: --append requires a filename', file=sys.stderr)
  218. sys.exit(1)
  219. elif not arg.startswith('--'):
  220. if totp_seed is None:
  221. totp_seed = arg
  222. i += 1
  223. else:
  224. print(f'[!] Warning: Unknown argument: {arg}', file=sys.stderr)
  225. i += 1
  226. try:
  227. cookies = login_and_get_cookies(username, password, totp_seed)
  228. session = {
  229. 'kind': 'cookie',
  230. 'username': cookies['username'],
  231. 'id': cookies.get('id'),
  232. 'auth_token': cookies['auth_token'],
  233. 'ct0': cookies['ct0']
  234. }
  235. output = json.dumps(session)
  236. if append_file:
  237. with open(append_file, 'a') as f:
  238. f.write(output + '\n')
  239. print(f'✓ Session appended to {append_file}', file=sys.stderr)
  240. else:
  241. print(output)
  242. sys.exit(0)
  243. except Exception as error:
  244. print(f'[!] Error: {error}', file=sys.stderr)
  245. import traceback
  246. traceback.print_exc(file=sys.stderr)
  247. sys.exit(1)
  248. if __name__ == '__main__':
  249. main()