···
+
# nitter-guest-account.py
+
# cross-platform port of https://github.com/zedeus/nitter/issues/983#issuecomment-1681199357
+
from base64 import b64encode
+
from argparse import ArgumentParser
+
print("\x1b[31m[!] Could not import `requests`.")
+
print("\x1b[31m[!] This script requires the requests module to be installed.")
+
print("\x1b[31m[!] We apologize but using plain http.client is way too painful."
+
" Please reach out with a PR if you would like to change that!")
+
CONSUMER_KEY = "3nVuSoBZnx6U4vzUxf5w"
+
CONSUMER_SECRET = "Bcs59EFbbsdF6Sl9Ng71smgStWEGwXXKSjYvPVt7qys"
+
EXPECTED_BEARER_TOKEN = "Bearer AAAAAAAAAAAAAAAAAAAAAFXzAwAAAAAAMHCxpeSDG1gLNLghVe8d74hl6k4%3DRUMF4xAQLsbeBhTSRrCiQpJtxoGWeyHrDb5te2jpGskWDFW82F"
+
BASE_REQUEST_HEADERS = {
+
'Content-Type': 'application/json',
+
'User-Agent': 'TwitterAndroid/9.95.0-release.0 (29950000-r-0) ONEPLUS+A3010/9 (OnePlus;ONEPLUS+A3010;OnePlus;OnePlus3;0;;1;2016)',
+
'X-Twitter-API-Version': '5',
+
'X-Twitter-Client': 'TwitterAndroid',
+
'X-Twitter-Client-Version': '9.95.0-release.0',
+
'System-User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 9; ONEPLUS A3010 Build/PKQ1.181203.001)',
+
'X-Twitter-Active-User': 'yes',
+
BASE_URL = "https://api.twitter.com"
+
BEARER_TOKEN_ENDPOINT = "/oauth2/token"
+
GUEST_TOKEN_ENDPOINT = "/1.1/guest/activate.json"
+
FLOW_TOKEN_ENDPOINT = "/1.1/onboarding/task.json?flow_name=welcome&api_version=1&known_device_token=&sim_country_code=us"
+
TASKS_ENDPOINT = "/1.1/onboarding/task.json"
+
def send_req(method, endpoint, **kwargs) -> requests.Response:
+
debug(f"attempting `{endpoint}`")
+
res = requests.request(method, BASE_URL + endpoint, **kwargs)
+
except requests.HTTPError:
+
error("HTTP request failed (non 2xx), unable to proceed.")
+
error('Please try again in a bit')
+
debug(f"request headers => {res.request.headers}")
+
debug(f"response headers => {res.headers}")
+
debug(f'response body => {res.content}')
+
error(f"an unhandled error while sending a request to {endpoint} occurred")
+
debug(f'got response body {res.content}')
+
"open_home_timeline": 1,
+
"app_locale_update": 1,
+
"email_verification": 3,
+
"fetch_persisted_data": 1,
+
"fetch_temporary_password": 1,
+
"user_recommendations_urt": 3,
+
"in_app_notification": 1,
+
"user_recommendations_list": 4,
+
"contacts_live_sync_permission_prompt": 3,
+
"js_instrumentation": 1,
+
"alert_dialog_suppress_client_events": 1,
+
"tweet_selection_urt": 1,
+
"open_external_link": 1,
+
"phone_verification": 5,
+
"check_logged_in_account": 1,
+
"location_permission_prompt": 2,
+
"notifications_permission_prompt": 4
+
def get_flow_token_body():
+
"location": "splash_screen"
+
"requested_variant": None,
+
"subtask_versions": B_SUBTASK_VERSIONS
+
def get_tasks_body(flow_token: str) -> dict:
+
"flow_token": flow_token,
+
"subtask_id": "NextTaskOpenLink"
+
"subtask_versions": B_SUBTASK_VERSIONS
+
def format_json(msg, object) -> str:
+
return json.dumps(object, indent=None if noprettyprint else 4)
+
def debug(msg, *arg, **kwarg) -> None:
+
print("\x1b[37m[*]", msg, *arg, "\x1b[0m", file=sys.stderr, **kwarg)
+
def info(msg, *arg, **kwarg) -> None:
+
print("\x1b[34m[i]", msg, *arg, "\x1b[0m", file=sys.stderr, **kwarg)
+
def success(msg, *arg, **kwarg) -> None:
+
print("\x1b[32m[i]", msg, *arg, "\x1b[0m", file=sys.stderr, **kwarg)
+
def warn(msg, *arg, **kwarg) -> None:
+
print("\x1b[33m[!]", msg, *arg, "\x1b[0m", file=sys.stderr, **kwarg)
+
def error(msg, *arg, **kwarg) -> None:
+
print("\x1b[31m[x]", msg, *arg, "\x1b[0m", file=sys.stderr, **kwarg)
+
parser = ArgumentParser()
+
parser.add_argument('-v', '--verbose', action='store_true', help="be more noisy")
+
parser.add_argument('-P', '--no-pretty', action='store_true', help="disable pretty-printing of json data")
+
help="the json output file to put/append received account data to."
+
args = parser.parse_args()
+
info("nitter-guest-account.py (2023-08-25)")
+
info("This is free software: you are free to change and redistribute it, under the terms of the Apache-2.0 license")
+
info("There is NO WARRANTY, to the extent permitted by law.")
+
info("Fetching bearer token...")
+
bt_raw = send_req('post', BEARER_TOKEN_ENDPOINT,
+
auth=requests.auth.HTTPBasicAuth(CONSUMER_KEY, CONSUMER_SECRET),
+
data={'grant_type': "client_credentials"}
+
bearer_token = ' '.join(bt_raw.values())
+
if bearer_token.lower() != EXPECTED_BEARER_TOKEN.lower():
+
warn('Received bearer token does not match expected value. Continuing anyways, but beware of errors.')
+
info(f'bearer token => {bearer_token}')
+
success('Received bearer token matches expected value.')
+
info("Fetching guest token...")
+
guest_token = send_req('post', GUEST_TOKEN_ENDPOINT, headers={'Authorization': bearer_token}).json()['guest_token']
+
success(f'guest token => {guest_token}')
+
debug('updating header with acquried credentials')
+
request_headers = BASE_REQUEST_HEADERS.copy()
+
request_headers.update({
+
"authorization": bearer_token,
+
"X-Guest-Token": guest_token
+
info('Fetching flow token...')
+
flow_token = send_req('post', FLOW_TOKEN_ENDPOINT, headers=request_headers, json=get_flow_token_body()).json()['flow_token']
+
success(f'flow token => {flow_token}')
+
info('Fetching final account object...')
+
tasks = send_req('post', TASKS_ENDPOINT, headers=request_headers, json=get_tasks_body(flow_token))
+
if __name__ == "__main__":