1import json 2import os 3import random 4import time 5import traceback 6 7import requests 8import requests.utils 9 10from . import config, devices 11 12# ====== SYNC METHODS ====== # 13 14 15def sync_device_features(self, login=False): 16 data = { 17 "id": self.uuid, 18 "server_config_retrieval": "1", 19 "experiments": config.LOGIN_EXPERIMENTS, 20 } 21 if login is False: 22 data["_uuid"] = self.uuid 23 data["_uid"] = self.user_id 24 data["_csrftoken"] = self.token 25 data = json.dumps(data) 26 self.last_experiments = time.time() 27 return self.send_request( 28 "qe/sync/", data, login=login, headers={"X-DEVICE-ID": self.uuid} 29 ) 30 31 32def sync_launcher(self, login=False): 33 data = { 34 "id": self.uuid, 35 "server_config_retrieval": "1", 36 } 37 if login is False: 38 data["_uid"] = self.user_id 39 data["_uuid"] = self.uuid 40 data["_csrftoken"] = self.token 41 data = json.dumps(data) 42 return self.send_request("launcher/sync/", data, login=login) 43 44 45def sync_user_features(self): 46 data = self.default_data 47 data["id"] = self.uuid 48 data["experiments"] = config.EXPERIMENTS 49 data = json.dumps(data) 50 self.last_experiments = time.time() 51 return self.send_request("qe/sync/", data, headers={"X-DEVICE-ID": self.uuid}) 52 53 54# "android_device_id":"android-f14b9731e4869eb", 55# "phone_id":"b4bd7978-ca2b-4ea0-a728-deb4180bd6ca", 56# "usages":"[\"account_recovery_omnibox\"]", 57# "_csrftoken":"9LZXBXXOztxNmg3h1r4gNzX5ohoOeBkI", 58# "device_id":"70db6a72-2663-48da-96f5-123edff1d458" 59def get_prefill_candidates(self, login=False): 60 data = { 61 "android_device_id": self.device_id, 62 "phone_id": self.phone_id, 63 "usages": '["account_recovery_omnibox"]', 64 "device_id": self.device_id, 65 } 66 if login is False: 67 data["_csrftoken"] = self.token 68 data = json.dumps(data) 69 return self.send_request("accounts/get_prefill_candidates/", data, login=login) 70 71 72def get_account_family(self): 73 return self.send_request("multiple_accounts/get_account_family/") 74 75 76def get_zr_token_result(self): 77 url = ( 78 "zr/token/result/?device_id={rank_token}" 79 "&token_hash=&custom_device_id={custom_device_id}&fetch_reason=token_expired" 80 ) 81 url = url.format(rank_token=self.device_id, custom_device_id=self.device_id) 82 return self.send_request(url) 83 84 85def banyan(self): 86 url = "banyan/banyan/?views=['story_share_sheet','threads_people_picker','group_stories_share_sheet','reshare_share_sheet']" 87 return self.send_request(url) 88 89 90def igtv_browse_feed(self): 91 url = "igtv/browse_feed/?prefetch=1" 92 return self.send_request(url) 93 94 95def creatives_ar_class(self): 96 data = { 97 "_csrftoken": self.token, 98 "_uuid": self.uuid, 99 } 100 data = json.dumps(data) 101 return self.send_request("creatives/ar_class/", data) 102 103 104# ====== LOGIN/PRE FLOWS METHODS ====== # 105 106 107def pre_login_flow(self): 108 self.logger.info("Not yet logged in starting: PRE-LOGIN FLOW!") 109 110 # /api/v1/accounts/get_prefill_candidates 111 self.get_prefill_candidates(True) 112 113 # /api/v1/qe/sync (server_config_retrieval) 114 self.sync_device_features(True) 115 116 # /api/v1/launcher/sync/ (server_config_retrieval) 117 self.sync_launcher(True) 118 119 # /api/v1/accounts/contact_point_prefill/ 120 self.set_contact_point_prefill("prefill") 121 122 # /api/v1/accounts/get_prefill_candidates/ 123 # self.get_prefill_candidates(True) 124 125 # /api/v1/accounts/contact_point_prefill/ 126 # self.set_contact_point_prefill("prefill") 127 128 # /api/v1/qe/sync/ (server_config_retrieval) 129 # self.sync_device_features(True) 130 131 # /api/v1/launcher/sync/ (server_config_retrieval) 132 # self.sync_launcher(True) 133 134 135# DO NOT MOVE ANY OF THE ENDPOINTS THEYRE IN THE CORRECT ORDER 136def login_flow(self, just_logged_in=False, app_refresh_interval=1800): 137 self.last_experiments = time.time() 138 self.logger.info("LOGIN FLOW! Just logged-in: {}".format(just_logged_in)) 139 check_flow = [] 140 if just_logged_in: 141 try: 142 # SYNC 143 # /api/v1/qe/sync/ (server_config_retrieval) 144 check_flow.append(self.sync_device_features()) 145 146 # /api/v1/launcher/sync/ (server_config_retrieval) 147 check_flow.append(self.sync_launcher(False)) 148 149 # /api/v1/zr/token/result/?device_id=android-f14b9731e4869eb&token_hash=&custom_device_id=f3119c98-5663-4c47-95b5-a63b140a2b62&fetch_reason=token_expired 150 check_flow.append(self.get_zr_token_result()) 151 152 # /api/v1/multiple_accounts/get_account_family/ 153 check_flow.append(self.get_account_family()) 154 155 # /api/v1/qe/sync/ (server_config_retrieval) 156 check_flow.append(self.sync_device_features()) 157 158 # /api/v1/banyan/banyan/?views=%5B%22story_share_sheet%22%2C%22threads_people_picker%22%2C%22group_stories_share_sheet%22%2C%22reshare_share_sheet%22%5D 159 # error 400: {"message": "Bad request", "status": "fail"} 160 # check_flow.append(self.banyan()) 161 162 # /api/v1/igtv/browse_feed/?prefetch=1 163 check_flow.append(self.igtv_browse_feed()) 164 165 # /api/v1/creatives/ar_class/ (_csrftoken also add _uuid) 166 check_flow.append(self.creatives_ar_class()) 167 168 # /api/v1/feed/reels_tray/ (supported_capabilities_new + reason=cold_start + _csrftoken + _uuid) 169 check_flow.append(self.get_reels_tray_feed(reason="cold_start")) 170 171 # /api/v1/feed/timeline/ (feed_view_info + phone_id + battery_level + timezone_offset + _csrftoken + device_id + request_id + is_pull_to_refresh=0 + _uuid + is_charging=1 + will_sound_on=0 + seesion_id + bloks_versioning_id) 172 check_flow.append(self.get_timeline_feed()) 173 174 # /api/v1/feed/reels_media/ 175 # error 400: {"message": "Invalid reel id list", "status": "fail"} 176 # check_flow.append(self.get_reels_media()) 177 178 # /api/v1/push/register/ (device_type=android_mqtt + is_main_push_channel=true + device_sub_type=2 + device_toke + _csrftoken + guid + _uuid + users + family_device_id) 179 # device_type=android_mqtt&is_main_push_channel=true&device_sub_type=2&device_token={"k":"eyJwbiI6ImNvbS5pbnN0YWdyYW0uYW5kcm9pZCIsImRpIjoiNzhlNGMxNmQtN2YzNC00NDlkLTg4OWMtMTAwZDg5OTU0NDJhIiwiYWkiOjU2NzMxMDIwMzQxNTA1MiwiY2siOiIxNjgzNTY3Mzg0NjQyOTQifQ==","v":0,"t":"fbns-b64"}&_csrftoken=mmdoMLXFQEzt2w5xLbfm0FTs7gIgqAlc&guid=f87b5e9f-0663-42f8-9213-ec72cb49c961&_uuid=f87b5e9f-0663-42f8-9213-ec72cb49c961&users=3149016955&family_device_id=9d9aa0f0-40fe-4524-a920-9910f45ba18d 180 # error 400: {"message": "no token provided", "status": "fail"} 181 # check_flow.append(self.push_register()) 182 183 # /api/v1/feed/reels_media/ 184 # error 400: {"message": "Invalid reel id list", "status": "fail"} 185 # check_flow.append(self.get_reels_media()) 186 187 # /api/v1/media/blocked/ 188 check_flow.append(self.media_blocked()) 189 190 # /api/v1/news/inbox/ 191 check_flow.append(self.get_news_inbox()) 192 193 # /api/v1/loom/fetch_config/ 194 check_flow.append(self.get_loom_fetch_config()) 195 196 # /api/v1/scores/bootstrap/users/?surfaces=%5B%22autocomplete_user_list%22%2C%22coefficient_besties_list_ranking%22%2C%22coefficient_rank_recipient_user_suggestion%22%2C%22coefficient_ios_section_test_bootstrap_ranking%22%2C%22coefficient_direct_recipients_ranking_variant_2%22%5D 197 check_flow.append(self.get_scores_bootstrap()) 198 199 # /api/v1/business/eligibility/get_monetization_products_eligibility_data/?product_types=branded_content 200 check_flow.append(self.get_monetization_products_eligibility_data()) 201 202 # /api/v1/business/branded_content/should_require_professional_account/ 203 check_flow.append(self.get_business_branded_content()) 204 205 # /api/v1/linked_accounts/get_linkage_status/ 206 check_flow.append(self.get_linked_accounts()) 207 208 # /api/v1/locations/request_country/ 209 check_flow.append(self.get_request_country()) 210 211 # /api/v1/qp/get_cooldowns/?signed_body=a7c6081ee2ae5b41a1475f83b6dbc8f1130c67d472f69748221468e1621823b5.%7B%7D&ig_sig_key_version=4 212 check_flow.append(self.get_cooldowns()) 213 214 # /api/v1/users/arlink_download_info/?version_override=2.2.1 215 check_flow.append(self.arlink_download_info()) 216 217 # push register 218 # error 400: {"message": "no token provided", "status": "fail"} 219 # check_flow.append(self.push_register()) 220 221 # /api/v1/users/self.user_id/info/ 222 check_flow.append(self.get_username_info(self.user_id)) 223 224 # /api/v1/notifications/store_client_push_permissions/ 225 # error 400: {"message": "missing param", "status": "fail"} 226 # check_flow.append(self.log_resurrect_attribution()) 227 228 # /api/v1/accounts/process_contact_point_signals 229 # error 400: {"message": "", "status": "fail"} 230 # check_flow.append(self.process_contact_point_signals()) 231 232 # creatives/write_supported_capabilities 233 # error 500: unknown 234 # check_flow.append(self.write_supported_capabilities()) 235 236 check_flow.append(self.get_presence()) 237 check_flow.append(self.get_direct_v2_inbox()) 238 check_flow.append(self.get_direct_v2_inbox2()) 239 check_flow.append(self.topical_explore()) 240 check_flow.append(self.notification_badge()) 241 check_flow.append(self.batch_fetch()) 242 243 # TODO add facebook_ota 244 # /api/v1/facebook_ota/?fields=update%7Bdownload_uri%2Cdownload_uri_delta_base%2Cversion_code_delta_base%2Cdownload_uri_delta%2Cfallback_to_full_update%2Cfile_size_delta%2Cversion_code%2Cpublished_date%2Cfile_size%2Cota_bundle_type%2Cresources_checksum%2Callowed_networks%2Crelease_id%7D&custom_user_id=3149016955&signed_body=656adcfd879d775324e9c1668534f80a999801c7780f16cc720d7970941195de.&ig_sig_key_version=4&version_code=195435566&version_name=126.0.0.25.121&custom_app_id=124024574287414&custom_device_id=f87b5e9f-0663-42f8-9213-ec72cb49c961 HTTP/1.1 245 # check_flow.append(self.facebook_ota()) 246 except Exception as e: 247 self.logger.error( 248 "Exception raised: {}\n{}".format(e, traceback.format_exc()) 249 ) 250 return False 251 else: 252 try: 253 pull_to_refresh = random.randint(1, 100) % 2 == 0 254 check_flow.append( 255 self.get_timeline_feed( 256 options=["is_pull_to_refresh"] if pull_to_refresh is True else [] 257 ) 258 ) # Random pull_to_refresh :) 259 check_flow.append( 260 self.get_reels_tray_feed( 261 reason="pull_to_refresh" 262 if pull_to_refresh is True 263 else "cold_start" 264 ) 265 ) 266 267 is_session_expired = (time.time() - self.last_login) > app_refresh_interval 268 if is_session_expired: 269 self.last_login = time.time() 270 self.client_session_id = self.generate_UUID(uuid_type=True) 271 272 # getBootstrapUsers() ... 273 check_flow.append(self.get_ranked_recipients("reshare", True)) 274 check_flow.append(self.get_ranked_recipients("save", True)) 275 check_flow.append(self.get_inbox_v2()) 276 check_flow.append(self.get_presence()) 277 check_flow.append(self.get_recent_activity()) 278 check_flow.append(self.get_profile_notice()) 279 check_flow.append(self.explore(False)) 280 281 if (time.time() - self.last_experiments) > 7200: 282 check_flow.append(self.sync_user_features()) 283 check_flow.append(self.sync_device_features()) 284 except Exception as e: 285 self.logger.error( 286 "Error loginin, exception raised: {}\n{}".format( 287 e, traceback.format_exc() 288 ) 289 ) 290 return False 291 292 self.save_uuid_and_cookie() 293 return False if False in check_flow else True 294 295 296# ====== DEVICE / CLIENT_ID / PHONE_ID AND OTHER VALUES (uuids) ====== # 297 298 299def set_device(self): 300 self.device_settings = devices.DEVICES[self.device] 301 self.user_agent = config.USER_AGENT_BASE.format(**self.device_settings) 302 303 304def generate_all_uuids(self): 305 self.phone_id = self.generate_UUID(uuid_type=True) 306 self.uuid = self.generate_UUID(uuid_type=True) 307 self.client_session_id = self.generate_UUID(uuid_type=True) 308 self.advertising_id = self.generate_UUID(uuid_type=True) 309 self.device_id = self.generate_device_id( 310 self.get_seed(self.username, self.password) 311 ) 312 313 314def reinstall_app_simulation(self): 315 self.logger.info("Reinstall app simulation, generating new `phone_id`...") 316 self.phone_id = self.generate_UUID(uuid_type=True) 317 self.save_uuid_and_cookie() 318 self.logger.info("New phone_id: {}".format(self.phone_id)) 319 320 321def change_device_simulation(self): 322 self.logger.info("Change device simulation") 323 self.reinstall_app_simulation() 324 self.logger.info("Generating new `android_device_id`...") 325 self.device_id = self.generate_device_id( 326 self.get_seed(self.generate_UUID(uuid_type=True)) 327 ) 328 self.save_uuid_and_cookie() 329 self.logger.info("New android_device_id: {}".format(self.device_id)) 330 331 332def load_uuid_and_cookie(self, load_uuid=True, load_cookie=True): 333 if self.cookie_fname is None: 334 fname = "{}_uuid_and_cookie.json".format(self.username) 335 self.cookie_fname = os.path.join(self.base_path, fname) 336 print(os.path.join(self.base_path, fname)) 337 338 if os.path.isfile(self.cookie_fname) is False: 339 return False 340 341 with open(self.cookie_fname, "r") as f: 342 data = json.load(f) 343 if "cookie" in data: 344 self.last_login = data["timing_value"]["last_login"] 345 self.last_experiments = data["timing_value"]["last_experiments"] 346 347 if load_cookie: 348 self.logger.debug("Loading cookies") 349 self.session.cookies = requests.utils.cookiejar_from_dict( 350 data["cookie"] 351 ) 352 cookie_username = self.cookie_dict["ds_user"] 353 assert cookie_username == self.username.lower() 354 self.cookie_dict["urlgen"] 355 356 if load_uuid: 357 self.logger.debug("Loading uuids") 358 self.phone_id = data["uuids"]["phone_id"] 359 self.uuid = data["uuids"]["uuid"] 360 self.client_session_id = data["uuids"]["client_session_id"] 361 self.advertising_id = data["uuids"]["advertising_id"] 362 self.device_id = data["uuids"]["device_id"] 363 364 self.device_settings = data["device_settings"] 365 self.user_agent = data["user_agent"] 366 367 msg = ( 368 "Recovery from {}: COOKIE {} - UUIDs {} - TIMING, DEVICE " 369 "and ...\n- user-agent={}\n- phone_id={}\n- uuid={}\n- " 370 "client_session_id={}\n- device_id={}" 371 ) 372 373 self.logger.info( 374 msg.format( 375 self.cookie_fname, 376 load_cookie, 377 load_uuid, 378 self.user_agent, 379 self.phone_id, 380 self.uuid, 381 self.client_session_id, 382 self.device_id, 383 ) 384 ) 385 else: 386 self.logger.info( 387 "The cookie seems to be the with the older structure. " 388 "Load and init again all uuids" 389 ) 390 self.session.cookies = requests.utils.cookiejar_from_dict(data) 391 self.last_login = time.time() 392 self.last_experiments = time.time() 393 cookie_username = self.cookie_dict["ds_user"] 394 assert cookie_username == self.username 395 self.set_device() 396 self.generate_all_uuids() 397 398 self.is_logged_in = True 399 return True 400 401 402def save_uuid_and_cookie(self): 403 if self.cookie_fname is None: 404 fname = "{}_uuid_and_cookie.json".format(self.username) 405 self.cookie_fname = os.path.join(self.base_path, fname) 406 407 data = { 408 "uuids": { 409 "phone_id": self.phone_id, 410 "uuid": self.uuid, 411 "client_session_id": self.client_session_id, 412 "advertising_id": self.advertising_id, 413 "device_id": self.device_id, 414 }, 415 "cookie": requests.utils.dict_from_cookiejar(self.session.cookies), 416 "timing_value": { 417 "last_login": self.last_login, 418 "last_experiments": self.last_experiments, 419 }, 420 "device_settings": self.device_settings, 421 "user_agent": self.user_agent, 422 } 423 with open(self.cookie_fname, "w") as f: 424 json.dump(data, f) 425