get_web_session.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. #!/usr/bin/env python3
  2. """
  3. Authenticates with X.com/Twitter and extracts session cookies for use with Nitter.
  4. Handles 2FA, extracts user info, and outputs clean JSON for sessions.jsonl.
  5. Requirements:
  6. pip install -r tools/requirements.txt
  7. Usage:
  8. python3 tools/get_web_session.py <username> <password> [totp_seed] [--append sessions.jsonl] [--headless]
  9. Examples:
  10. # Output to terminal
  11. python3 tools/get_web_session.py myusername mypassword TOTP_BASE32_SECRET
  12. # Append to sessions.jsonl
  13. python3 tools/get_web_session.py myusername mypassword TOTP_SECRET --append sessions.jsonl
  14. # Headless mode (may increase detection risk)
  15. python3 tools/get_web_session.py myusername mypassword TOTP_SECRET --headless
  16. Output:
  17. {"kind": "cookie", "username": "...", "id": "...", "auth_token": "...", "ct0": "..."}
  18. """
  19. import sys
  20. import json
  21. import asyncio
  22. import pyotp
  23. import nodriver as uc
  24. import os
  25. async def login_and_get_cookies(username, password, totp_seed=None, headless=False):
  26. """Authenticate with X.com and extract session cookies"""
  27. # Note: headless mode may increase detection risk from bot-detection systems
  28. browser = await uc.start(headless=headless)
  29. tab = await browser.get('https://x.com/i/flow/login')
  30. try:
  31. # Enter username
  32. print('[*] Entering username...', file=sys.stderr)
  33. username_input = await tab.find('input[autocomplete="username"]', timeout=10)
  34. await username_input.send_keys(username + '\n')
  35. await asyncio.sleep(1)
  36. # Enter password
  37. print('[*] Entering password...', file=sys.stderr)
  38. password_input = await tab.find('input[autocomplete="current-password"]', timeout=15)
  39. await password_input.send_keys(password + '\n')
  40. await asyncio.sleep(2)
  41. # Handle 2FA if needed
  42. page_content = await tab.get_content()
  43. if 'verification code' in page_content or 'Enter code' in page_content:
  44. if not totp_seed:
  45. raise Exception('2FA required but no TOTP seed provided')
  46. print('[*] 2FA detected, entering code...', file=sys.stderr)
  47. totp_code = pyotp.TOTP(totp_seed).now()
  48. code_input = await tab.select('input[type="text"]')
  49. await code_input.send_keys(totp_code + '\n')
  50. await asyncio.sleep(3)
  51. # Get cookies
  52. print('[*] Retrieving cookies...', file=sys.stderr)
  53. for _ in range(20): # 20 second timeout
  54. cookies = await browser.cookies.get_all()
  55. cookies_dict = {cookie.name: cookie.value for cookie in cookies}
  56. if 'auth_token' in cookies_dict and 'ct0' in cookies_dict:
  57. print('[*] Found both cookies', file=sys.stderr)
  58. # Extract ID from twid cookie (may be URL-encoded)
  59. user_id = None
  60. if 'twid' in cookies_dict:
  61. twid = cookies_dict['twid']
  62. # Try to extract the ID from twid (format: u%3D<id> or u=<id>)
  63. if 'u%3D' in twid:
  64. user_id = twid.split('u%3D')[1].split('&')[0].strip('"')
  65. elif 'u=' in twid:
  66. user_id = twid.split('u=')[1].split('&')[0].strip('"')
  67. cookies_dict['username'] = username
  68. if user_id:
  69. cookies_dict['id'] = user_id
  70. return cookies_dict
  71. await asyncio.sleep(1)
  72. raise Exception('Timeout waiting for cookies')
  73. finally:
  74. browser.stop()
  75. async def main():
  76. if len(sys.argv) < 3:
  77. print('Usage: python3 twitter-auth.py username password [totp_seed] [--append sessions.jsonl] [--headless]')
  78. sys.exit(1)
  79. username = sys.argv[1]
  80. password = sys.argv[2]
  81. totp_seed = None
  82. append_file = None
  83. headless = False
  84. # Parse optional arguments
  85. i = 3
  86. while i < len(sys.argv):
  87. arg = sys.argv[i]
  88. if arg == '--append':
  89. if i + 1 < len(sys.argv):
  90. append_file = sys.argv[i + 1]
  91. i += 2 # Skip '--append' and filename
  92. else:
  93. print('[!] Error: --append requires a filename', file=sys.stderr)
  94. sys.exit(1)
  95. elif arg == '--headless':
  96. headless = True
  97. i += 1
  98. elif not arg.startswith('--'):
  99. if totp_seed is None:
  100. totp_seed = arg
  101. i += 1
  102. else:
  103. # Unkown args
  104. print(f'[!] Warning: Unknown argument: {arg}', file=sys.stderr)
  105. i += 1
  106. try:
  107. cookies = await login_and_get_cookies(username, password, totp_seed, headless)
  108. session = {
  109. 'kind': 'cookie',
  110. 'username': cookies['username'],
  111. 'id': cookies.get('id'),
  112. 'auth_token': cookies['auth_token'],
  113. 'ct0': cookies['ct0']
  114. }
  115. output = json.dumps(session)
  116. if append_file:
  117. with open(append_file, 'a') as f:
  118. f.write(output + '\n')
  119. print(f'✓ Session appended to {append_file}', file=sys.stderr)
  120. else:
  121. print(output)
  122. os._exit(0)
  123. except Exception as error:
  124. print(f'[!] Error: {error}', file=sys.stderr)
  125. sys.exit(1)
  126. if __name__ == '__main__':
  127. asyncio.run(main())