1// adapted from https://github.com/zedeus/nitter/issues/983#issuecomment-1681199357 2// deno run --allow-{env,read,net} nitter-guest-account.js 3 4import axios from 'npm:axios' 5import chalk from 'npm:chalk' 6const TW_CONSUMER_KEY = '3nVuSoBZnx6U4vzUxf5w' 7const TW_CONSUMER_SECRET = 'Bcs59EFbbsdF6Sl9Ng71smgStWEGwXXKSjYvPVt7qys' 8 9const TW_ANDROID_BASIC_TOKEN = `Basic ${btoa(TW_CONSUMER_KEY+':'+TW_CONSUMER_SECRET)}` 10 11const VERBOSE_MODE = Deno.args.includes('-v') || Deno.args.includes("--verbose") || Deno.args.includes('--debug') || Deno.args.includes('-d') 12const NO_PRETTYPRINT = Deno.args.includes('-P') || Deno.args.includes("--no-pretty") 13 14function debug(msg) { 15 if (VERBOSE_MODE) { 16 console.log(chalk.gray(`[*] ${msg}`)) 17 } 18} 19 20const getBearerToken = async () => { 21 console.error(chalk.blue("[i] Getting bearer token...")); 22 const tmpTokenResponse = await axios('https://api.twitter.com/oauth2/token', { 23 headers: { 24 Authorization: TW_ANDROID_BASIC_TOKEN, 25 'Content-Type': 'application/x-www-form-urlencoded' 26 }, 27 method: 'post', 28 data: 'grant_type=client_credentials' 29 }); 30 return Object.values(tmpTokenResponse.data).join(" "); 31} 32// The bearer token is immutable 33// Bearer AAAAAAAAAAAAAAAAAAAAAFXzAwAAAAAAMHCxpeSDG1gLNLghVe8d74hl6k4%3DRUMF4xAQLsbeBhTSRrCiQpJtxoGWeyHrDb5te2jpGskWDFW82F 34const bearer_token = await getBearerToken() 35console.error(chalk.blue(`[i] bearer token = ${bearer_token}`)); 36if (bearer_token.toLowerCase() !== "Bearer AAAAAAAAAAAAAAAAAAAAAFXzAwAAAAAAMHCxpeSDG1gLNLghVe8d74hl6k4%3DRUMF4xAQLsbeBhTSRrCiQpJtxoGWeyHrDb5te2jpGskWDFW82F".toLowerCase()) { 37 console.warn(chalk.yellow("[!] bearer token does not match expected value")) 38} else { 39 console.error(chalk.green('[i] bearer token matches expected value')) 40} 41 42console.error(chalk.blue(`[i] getting guest token`)) 43const guest_token = (await axios("https://api.twitter.com/1.1/guest/activate.json", { 44 headers: { 45 Authorization: bearer_token 46 }, 47 method: "post" 48})).data.guest_token 49console.error(chalk.blue(`[i] guest token = ${guest_token}`)); 50 51console.error(chalk.blue(`[i] getting flow_token`)) 52const flow_token_r = (await axios('https://api.twitter.com/1.1/onboarding/task.json?flow_name=welcome&api_version=1&known_device_token=&sim_country_code=us', { 53 headers: { 54 Authorization: bearer_token, 55 'Content-Type': 'application/json', 56 'User-Agent': 'TwitterAndroid/9.95.0-release.0 (29950000-r-0) ONEPLUS+A3010/9 (OnePlus;ONEPLUS+A3010;OnePlus;OnePlus3;0;;1;2016)', 57 'X-Twitter-API-Version': 5, 58 'X-Twitter-Client': 'TwitterAndroid', 59 'X-Twitter-Client-Version': '9.95.0-release.0', 60 'OS-Version': '28', 61 'System-User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 9; ONEPLUS A3010 Build/PKQ1.181203.001)', 62 'X-Twitter-Active-User': 'yes', 63 'X-Guest-Token': guest_token 64 }, 65 method: 'post', 66 data: '{"flow_token":null,"input_flow_data":{"country_code":null,"flow_context":{"start_location":{"location":"splash_screen"}},"requested_variant":null,"target_user_id":0},"subtask_versions":{"generic_urt":3,"standard":1,"open_home_timeline":1,"app_locale_update":1,"enter_date":1,"email_verification":3,"enter_password":5,"enter_text":5,"one_tap":2,"cta":7,"single_sign_on":1,"fetch_persisted_data":1,"enter_username":3,"web_modal":2,"fetch_temporary_password":1,"menu_dialog":1,"sign_up_review":5,"interest_picker":4,"user_recommendations_urt":3,"in_app_notification":1,"sign_up":2,"typeahead_search":1,"user_recommendations_list":4,"cta_inline":1,"contacts_live_sync_permission_prompt":3,"choice_selection":5,"js_instrumentation":1,"alert_dialog_suppress_client_events":1,"privacy_options":1,"topics_selector":1,"wait_spinner":3,"tweet_selection_urt":1,"end_flow":1,"settings_list":7,"open_external_link":1,"phone_verification":5,"security_key":3,"select_banner":2,"upload_media":1,"web":2,"alert_dialog":1,"open_account":2,"action_list":2,"enter_phone":2,"open_link":1,"show_code":1,"update_users":1,"check_logged_in_account":1,"enter_email":2,"select_avatar":4,"location_permission_prompt":2,"notifications_permission_prompt":4}}' 67})).data 68const flow_token = flow_token_r.flow_token 69debug(`flow_token => ${JSON.stringify(flow_token_r, null, 2)}`) 70console.error(chalk.blue(`[i] flow_token = ${flow_token}`)) 71 72const tasks_raw = (await axios('https://api.twitter.com/1.1/onboarding/task.json', { 73 headers: { 74 Authorization: bearer_token, 75 'Content-Type': 'application/json', 76 'User-Agent': 'TwitterAndroid/9.95.0-release.0 (29950000-r-0) ONEPLUS+A3010/9 (OnePlus;ONEPLUS+A3010;OnePlus;OnePlus3;0;;1;2016)', 77 'X-Twitter-API-Version': 5, 78 'X-Twitter-Client': 'TwitterAndroid', 79 'X-Twitter-Client-Version': '9.95.0-release.0', 80 'OS-Version': '28', 81 'System-User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 9; ONEPLUS A3010 Build/PKQ1.181203.001)', 82 'X-Twitter-Active-User': 'yes', 83 'X-Guest-Token': guest_token 84 }, 85 method: 'post', 86 data: '{"flow_token":"' + flow_token + '","subtask_inputs":[{"open_link":{"link":"next_link"},"subtask_id":"NextTaskOpenLink"}],"subtask_versions":{"generic_urt":3,"standard":1,"open_home_timeline":1,"app_locale_update":1,"enter_date":1,"email_verification":3,"enter_password":5,"enter_text":5,"one_tap":2,"cta":7,"single_sign_on":1,"fetch_persisted_data":1,"enter_username":3,"web_modal":2,"fetch_temporary_password":1,"menu_dialog":1,"sign_up_review":5,"interest_picker":4,"user_recommendations_urt":3,"in_app_notification":1,"sign_up":2,"typeahead_search":1,"user_recommendations_list":4,"cta_inline":1,"contacts_live_sync_permission_prompt":3,"choice_selection":5,"js_instrumentation":1,"alert_dialog_suppress_client_events":1,"privacy_options":1,"topics_selector":1,"wait_spinner":3,"tweet_selection_urt":1,"end_flow":1,"settings_list":7,"open_external_link":1,"phone_verification":5,"security_key":3,"select_banner":2,"upload_media":1,"web":2,"alert_dialog":1,"open_account":2,"action_list":2,"enter_phone":2,"open_link":1,"show_code":1,"update_users":1,"check_logged_in_account":1,"enter_email":2,"select_avatar":4,"location_permission_prompt":2,"notifications_permission_prompt":4}}' 87})).data 88 89debug(`tasks => ${JSON.stringify(tasks_raw, null, 2)}`); 90const subtasks = tasks_raw.subtasks; 91const account = subtasks.find(task => task.subtask_id === 'OpenAccount')?.open_account 92 93async function tryAppendJSON(file, content) { 94 95 async function tryRead(file, _default = "[]") { 96 try { 97 const rawD = await Deno.readTextFile(file); 98 return rawD 99 } catch (e) { 100 if (e?.code === "ENOENT") { 101 return _default 102 } else { 103 throw e 104 } 105 } 106 } 107 108 if (file) { 109 debug(`attempting to write to file ${file}`) 110 try { 111 const rD = await tryRead(file) 112 debug("read old file with contents => " + rD); 113 const oldData = JSON.parse(rD) 114 if (!Array.isArray(oldData)) { 115 console.error(chalk.red(`[!] top-level object of existing file ${file} is not an array, not proceeding.`)) 116 return false; 117 } 118 oldData.push(content) 119 await Deno.writeTextFile(file, JSON.stringify(oldData, null, 4)) 120 return true 121 } catch (e) { 122 console.error(chalk.red("[!] Uncaught error", e)) 123 return false 124 } 125 } else { 126 debug("file is not truthy, bailing") 127 return false 128 } 129} 130 131if (!account) { 132 console.error(chalk.red(`[!] unable to acquire account token. API response => ${JSON.stringify(account)}`)) 133 console.error(chalk.red(`[!] this might be because of a plethora of reasons, but most likely it's due to your IP being rate-limited.`)) 134 console.error(chalk.red(`[!] try again with a new IP address or retry in a day.`)) 135 console.info(chalk.yellow(`[i] not writing to file because we did not receive a valid account object.`)) 136} else { 137 const outfile = Deno.args.find(i => i.match(/^(?!-).*$/)); 138 if (!await tryAppendJSON(outfile, account)) { 139 debug("appending to file failed, printing to stdout instead.") 140 if (NO_PRETTYPRINT) { 141 console.log(JSON.stringify(account)) 142 } else { 143 console.log(account) 144 } 145 } 146}