From 6acd2d35ef6f12d4703ab13ca0d9d883bb53321b Mon Sep 17 00:00:00 2001 From: Alexander Mishchenko Date: Sun, 20 Jun 2021 20:38:42 +0300 Subject: [PATCH] Update to v3.7.13 --- MANIFEST.in | 1 + insomniac/__version__.py | 2 +- insomniac/actions_impl.py | 109 ++++++++------------------------- insomniac/activation.py | 3 +- insomniac/db_models.py | 16 +++-- insomniac/migration.py | 5 +- insomniac/safely_runner.py | 24 ++++++++ insomniac/softban_indicator.py | 5 +- insomniac/views.py | 102 ++++++++++++++++++++++++------ registration/api.py | 72 +++++++++++++++++++++- 10 files changed, 222 insertions(+), 117 deletions(-) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..818fa71 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +graft insomniac/apk diff --git a/insomniac/__version__.py b/insomniac/__version__.py index abd933a..cf56a60 100644 --- a/insomniac/__version__.py +++ b/insomniac/__version__.py @@ -13,7 +13,7 @@ __title__ = 'insomniac' __description__ = 'Simple Instagram bot for automated Instagram interaction using Android.' __url__ = 'https://github.com/alexal1/Insomniac/' -__version__ = '3.7.12' +__version__ = '3.7.13' __debug_mode__ = False __author__ = 'Insomniac Team' __author_email__ = 'info@insomniac-bot.com' diff --git a/insomniac/actions_impl.py b/insomniac/actions_impl.py index b2d6dd3..19b8d21 100644 --- a/insomniac/actions_impl.py +++ b/insomniac/actions_impl.py @@ -9,8 +9,7 @@ from insomniac.softban_indicator import softban_indicator from insomniac.tools.spintax import spin from insomniac.utils import * -from insomniac.validations import validate_ig_profile_existence -from insomniac.views import ActionBarView, ProfileView, PostsViewList, OpenedPostView, LikersListView +from insomniac.views import ActionBarView, ProfileView, PostsViewList, OpenedPostView, LikersListView, DialogView FOLLOWERS_BUTTON_ID_REGEX = '{0}:id/row_profile_header_followers_container' \ '|{1}:id/row_profile_header_container_followers' @@ -18,9 +17,6 @@ FOLLOW_REGEX = 'Follow|Follow Back' ALREADY_FOLLOWING_REGEX = 'Following|Requested' SHOP_REGEX = 'Add Shop|View Shop' -UNFOLLOW_REGEX = 'Unfollow' -FOLLOWING_BUTTON_ID_REGEX = '{0}:id/row_profile_header_following_container' \ - '|{1}:id/row_profile_header_container_following' USER_AVATAR_VIEW_ID = '{0}:id/circular_image|^$' LISTVIEW_OR_RECYCLERVIEW_REGEX = 'android.widget.ListView|androidx.recyclerview.widget.RecyclerView' @@ -596,18 +592,11 @@ def _open_user(device, username, open_followers=False, open_followings=False, if username is None: if open_followers: print("Open your followers") - followers_button = device.find(resourceIdMatches=FOLLOWERS_BUTTON_ID_REGEX.format(device.app_id, device.app_id)) - followers_button.click() + ProfileView(device, is_own_profile=True).navigate_to_followers() if open_followings: - print("Open your followings") - followings_button = device.find(resourceIdMatches=FOLLOWING_BUTTON_ID_REGEX.format(device.app_id, device.app_id)) - followings_button.click() - sleeper.random_sleep() - followings_tab = device.find(classNameMatches=TEXTVIEW_OR_BUTTON_REGEX, - clickable=True, - textContains=' Following') - followings_tab.click() + print("Open your following") + ProfileView(device, is_own_profile=True).navigate_to_following() else: should_open_user_with_search = True deep_link_usage_chance = randint(1, 100) @@ -635,18 +624,11 @@ def _open_user(device, username, open_followers=False, open_followings=False, if open_followers: print("Open @" + username + " followers") - followers_button = device.find(resourceIdMatches=FOLLOWERS_BUTTON_ID_REGEX.format(device.app_id, device.app_id)) - followers_button.click() + ProfileView(device, is_own_profile=True).navigate_to_followers() if open_followings: - print("Open @" + username + " followings") - followings_button = device.find(resourceIdMatches=FOLLOWING_BUTTON_ID_REGEX.format(device.app_id, device.app_id)) - followings_button.click() - sleeper.random_sleep() - followings_tab = device.find(classNameMatches=TEXTVIEW_OR_BUTTON_REGEX, - clickable=True, - textContains=' Following') - followings_tab.click() + print("Open @" + username + " following") + ProfileView(device, is_own_profile=True).navigate_to_following() return True @@ -762,12 +744,17 @@ def sort_followings_by_date(device, sort_order): return if sort_order == FollowingsSortOrder.DEFAULT: - sort_options_recycler_view.child(index=0).child(index=1).click() + sort_item = sort_options_recycler_view.child(index=0) elif sort_order == FollowingsSortOrder.LATEST: - sort_options_recycler_view.child(index=1).child(index=1).click() + sort_item = sort_options_recycler_view.child(index=1) else: # EARLIEST - sort_options_recycler_view.child(index=2).child(index=1).click() + sort_item = sort_options_recycler_view.child(index=2) + if not sort_item.exists(): + print(COLOR_FAIL + f"Cannot find an option to sort by {sort_order.name}" + COLOR_ENDC) + device.back() + return + sort_item.click() def do_unfollow(device, my_username, username, storage, check_if_is_follower, username_view, follow_status_button_view, on_action): """ @@ -782,7 +769,7 @@ def do_unfollow(device, my_username, username, storage, check_if_is_follower, us print("Unfollowing a profile directly from the following list.") follow_status_button_view.click() else: - print("Unfollowing a profile from his profile page.") + print("Unfollowing a profile from their profile page.") username_view.click() on_action(GetProfileAction(user=username)) sleeper.random_sleep() @@ -813,21 +800,18 @@ def do_unfollow(device, my_username, username, storage, check_if_is_follower, us print(f"Unfollowing @{username}...") unfollow_button.click() - sleeper.random_sleep() - confirm_unfollow_button = device.find(resourceId=f'{device.app_id}:id/follow_sheet_unfollow_row', - className='android.widget.TextView') - if not confirm_unfollow_button.exists(): - print(COLOR_FAIL + "Cannot confirm unfollow." + COLOR_ENDC) - save_crash(device) - device.back() - return False - confirm_unfollow_button.click() + unfollow_confirmed = False + dialog_view = DialogView(device) + if dialog_view.is_visible(): + print("Confirming unfollow...") + unfollow_confirmed = dialog_view.click_unfollow() - sleeper.random_sleep() - _close_confirm_dialog_if_shown(device) - softban_indicator.detect_action_blocked_dialog(device) + if unfollow_confirmed: + sleeper.random_sleep() + else: + softban_indicator.detect_action_blocked_dialog(device) if need_to_go_back_to_list: print("Back to the followings list.") @@ -872,9 +856,7 @@ def interact_with_feed(navigate_to_feed, should_continue, interact_with_feed_pos def _check_is_follower(device, username, my_username): print(COLOR_OKGREEN + "Check if @" + username + " is following you." + COLOR_ENDC) - following_container = device.find(resourceIdMatches=FOLLOWING_BUTTON_ID_REGEX.format(device.app_id, device.app_id)) - following_container.click() - + ProfileView(device, is_own_profile=True).navigate_to_following() sleeper.random_sleep() is_list_empty = softban_indicator.detect_empty_list(device) @@ -895,45 +877,6 @@ def _check_is_follower(device, username, my_username): return result -def _close_confirm_dialog_if_shown(device): - if not _close_confirm_dialog_by_version(device, 2): - _close_confirm_dialog_by_version(device, 1) - - -def _close_confirm_dialog_by_version(device, version): - if version == 1: - dialog_root_view = device.find(resourceId=f'{device.app_id}:id/dialog_root_view', - className='android.widget.FrameLayout') - elif version == 2: - dialog_root_view = device.find(resourceId=f'{device.app_id}:id/dialog_container', - className='android.view.ViewGroup') - else: - raise ValueError("Close unfollow confrim dialog for vis not exists.") - - if not dialog_root_view.exists(quick=True): - return False - - # Avatar existence is the way to distinguish confirm dialog from block dialog - user_avatar_view = device.find(resourceIdMatches=USER_AVATAR_VIEW_ID.format(device.app_id), - className='android.widget.ImageView') - if not user_avatar_view.exists(quick=True): - return False - - print(COLOR_OKGREEN + "Dialog shown, confirm unfollowing." + COLOR_ENDC) - sleeper.random_sleep() - if version == 1: - unfollow_button = dialog_root_view.child(resourceId=f'{device.app_id}:id/primary_button', - classNameMatches=TEXTVIEW_OR_BUTTON_REGEX) - elif version == 2: - unfollow_button = dialog_root_view.child(resourceId=f'{device.app_id}:id/primary_button', - classNameMatches=TEXTVIEW_OR_BUTTON_REGEX, - textMatches=UNFOLLOW_REGEX) - - unfollow_button.click() - sleeper.random_sleep() - return True - - def _get_action_bar(device): tab_bar = device.find( resourceIdMatches=case_insensitive_re( diff --git a/insomniac/activation.py b/insomniac/activation.py index 6b138a1..2c62fa7 100644 --- a/insomniac/activation.py +++ b/insomniac/activation.py @@ -29,10 +29,11 @@ def validate(self, activation_code, ui=False): f"{dot}{COLOR_BOLD}Filtering{COLOR_ENDC} - skip unwanted accounts by various parameters" f"{dot}{COLOR_BOLD}Scrapping{COLOR_ENDC} - technique that makes interactions " f"significantly safer and faster" + f"{dot}{COLOR_BOLD}Warmup{COLOR_ENDC} - interact with your feed and Explore several minutes " + f"before session to behave more like a human" f"{dot}{COLOR_BOLD}Working hours{COLOR_ENDC} - the script will wait till specified hours " f"before each session" f"{dot}{COLOR_BOLD}Removing mass followers{COLOR_ENDC} - automate \"cleaning\" you account" - f"{dot}{COLOR_BOLD}Analytics tool{COLOR_ENDC} - build presentation that shows your growth\n" f"Activate by supporting our small team: {COLOR_BOLD}{HOST}{PATH_ACTIVATE}{COLOR_ENDC}\n") def get_extra_feature(self, module, ui=False): diff --git a/insomniac/db_models.py b/insomniac/db_models.py index 68a7c89..db01874 100644 --- a/insomniac/db_models.py +++ b/insomniac/db_models.py @@ -623,22 +623,22 @@ class ProfileStatus(Enum): ] -def init() -> bool: +def init(): """ - Initialize database and return whether it was just created. + Initialize database. """ with db.connection_context(): if len(db.get_tables()) == 0: print(f"Creating tables in {DATABASE_NAME}...") db.create_tables(MODELS) SchemaVersion.create() - return True + return # Migration logic current_db_version = _get_db_version() if current_db_version is None: print(COLOR_FAIL + "Cannot read database version from SchemaVersion table!" + COLOR_ENDC) - return False + return if current_db_version > DATABASE_VERSION: raise Exception("Bot version is too low, cannot work with the current database! " @@ -650,8 +650,6 @@ def init() -> bool: migrator = SqliteMigrator(db) _migrate(current_db_version, migrator) current_db_version = _get_db_version() - return False - return False def get_ig_profile_by_profile_name(profile_name) -> InstagramProfile: @@ -660,6 +658,12 @@ def get_ig_profile_by_profile_name(profile_name) -> InstagramProfile: return profile +def is_ig_profile_exists(profile_name) -> bool: + with db.connection_context(): + is_exists = InstagramProfile.select().where(InstagramProfile.name == profile_name).exists() + return is_exists + + def get_ig_profiles_actions_by_task_id(actions_task_id, action_types_list) -> dict: """Returns a dict of ig_profile_name -> list of InsomniacAction objects""" ig_profiles_to_actions = defaultdict(list) diff --git a/insomniac/migration.py b/insomniac/migration.py index 236a270..a434884 100644 --- a/insomniac/migration.py +++ b/insomniac/migration.py @@ -1,6 +1,6 @@ import json -from insomniac.db_models import DATABASE_NAME +from insomniac.db_models import DATABASE_NAME, is_ig_profile_exists from insomniac.session_state import SessionState from insomniac.sessions import FILENAME_SESSIONS, Sessions from insomniac.storage import * @@ -155,7 +155,8 @@ def migrate_from_sql_to_peewee(my_username): if not check_database_exists(my_username, False): return - if not db_models.init(): + db_models.init() + if is_ig_profile_exists(my_username): return database = get_database(my_username) diff --git a/insomniac/safely_runner.py b/insomniac/safely_runner.py index d39e214..0efbac9 100644 --- a/insomniac/safely_runner.py +++ b/insomniac/safely_runner.py @@ -2,6 +2,8 @@ from socket import timeout from insomniac.device_facade import DeviceFacade +from insomniac.extra_features.actions_impl import airplane_mode_on_off +from insomniac.extra_features.utils import new_identity from insomniac.navigation import navigate, LanguageChangedException from insomniac.sleeper import sleeper from insomniac.utils import * @@ -37,3 +39,25 @@ def wrapper(*args, **kwargs): navigate(device_wrapper.get(), TabBarTabs.PROFILE) return wrapper return actual_decorator + + +def run_registration_safely(device_wrapper): + def actual_decorator(func): + def wrapper(*args, **kwargs): + try: + func(*args, **kwargs) + except (DeviceFacade.JsonRpcError, IndexError, HTTPException, timeout) as ex: + print(COLOR_FAIL + describe_exception(ex) + COLOR_ENDC) + save_crash(device_wrapper.get(), ex) + print("No idea what it was. Let's try again.") + + # For the registration flow we create new identity and turn airplane mode on-and-off before each + # Instagram app restart + new_identity(device_wrapper.device_id, device_wrapper.app_id) + sleeper.random_sleep(multiplier=2.0) + close_instagram(device_wrapper.device_id, device_wrapper.app_id) + airplane_mode_on_off(device_wrapper.get()) + open_instagram(device_wrapper.device_id, device_wrapper.app_id) + sleeper.random_sleep() + return wrapper + return actual_decorator diff --git a/insomniac/softban_indicator.py b/insomniac/softban_indicator.py index 62de969..ed1a573 100644 --- a/insomniac/softban_indicator.py +++ b/insomniac/softban_indicator.py @@ -1,7 +1,7 @@ from enum import unique, Enum from insomniac.utils import * -from insomniac.views import ProfileView, FollowersFollowingListView, InstagramView +from insomniac.views import ProfileView, FollowersFollowingListView, DialogView EMPTY_LIST_TRESHOLD = 5 EMPTY_PROFILE_TRESHOLD = 5 @@ -74,8 +74,7 @@ def detect_empty_profile(self, device): @check_softban_feature_flag def detect_action_blocked_dialog(self, device): - curr_view = InstagramView(device) - is_blocked = curr_view.is_block_dialog_present() + is_blocked = DialogView(device).is_visible() if is_blocked: print(COLOR_FAIL + "Probably block dialog is shown. " "Counting that as a soft-ban indicator!." + COLOR_ENDC) diff --git a/insomniac/views.py b/insomniac/views.py index 0d89974..22c9d6b 100644 --- a/insomniac/views.py +++ b/insomniac/views.py @@ -1,5 +1,5 @@ import datetime -from enum import Enum +from enum import Enum, unique from typing import Optional from insomniac.actions_types import GetProfileAction @@ -491,16 +491,10 @@ def navigate_to_place(self, place): return PlacesView(self.device) def _handle_permission_request(self): - dialog_container = self.device.find(resourceId="com.android.packageinstaller:id/dialog_container", - className="android.widget.LinearLayout") - deny_button = dialog_container.child(resourceId="com.android.packageinstaller:id/permission_deny_button", - className="android.widget.Button") - checkbox = dialog_container.child(resourceId="com.android.packageinstaller:id/do_not_ask_checkbox", - className="android.widget.CheckBox") - if dialog_container.exists(quick=True): - print("Deny Instagram permission request") - checkbox.click(ignore_if_missing=True) - deny_button.click(ignore_if_missing=True) + dialog_view = DialogView(self.device) + if dialog_view.is_visible(): + print("Deny location permission request") + dialog_view.click_deny_location_access() class PostsViewList(InstagramView): @@ -902,8 +896,11 @@ def navigate_to_actions(self) -> 'ProfileActionsView': def change_to_username(self, username): action_bar = self._get_action_bar_title_btn() - current_profile_name = action_bar.get_text().upper() - if current_profile_name == username.upper(): + + def is_username_profile_opened(): + return action_bar.get_text().strip().upper() == username.upper() + + if is_username_profile_opened(): print(COLOR_OKBLUE + f"You are already logged as {username}!" + COLOR_ENDC) return True @@ -917,8 +914,7 @@ def change_to_username(self, username): print(f"Switching to {username}...") found_obj.click() sleeper.random_sleep() - current_profile_name = action_bar.get_text().upper() - if current_profile_name == username.upper(): + if is_username_profile_opened(): return True return False @@ -1120,15 +1116,17 @@ def navigate_to_followers(self): print_debug("Navigate to Followers") followers_button = self.device.find(resourceIdMatches=self.FOLLOWERS_BUTTON_ID_REGEX.format(self.device.app_id, self.device.app_id)) followers_button.click() - - return FollowersFollowingListView(self.device) + followers_following_list_view = FollowersFollowingListView(self.device) + followers_following_list_view.switch_to_tab(FollowersFollowingListView.Tab.FOLLOWERS) + return followers_following_list_view def navigate_to_following(self): print_debug("Navigate to Followers") following_button = self.device.find(resourceIdMatches=self.FOLLOWING_BUTTON_ID_REGEX.format(self.device.app_id, self.device.app_id)) following_button.click() - - return FollowersFollowingListView(self.device) + followers_following_list_view = FollowersFollowingListView(self.device) + followers_following_list_view.switch_to_tab(FollowersFollowingListView.Tab.FOLLOWING) + return followers_following_list_view def open_messages(self): message_button = self.device.find( @@ -1155,6 +1153,28 @@ def open_messages(self): class FollowersFollowingListView(InstagramView): + + @unique + class Tab(Enum): + FOLLOWERS = 0 + FOLLOWING = 1 + + def switch_to_tab(self, tab): + """ + :type tab: FollowersFollowingListView.Tab + """ + sleeper.random_sleep() + following_tab = self.device.find(className="android.widget.TextView", + clickable=True, + textContains="Following") + followers_tab = self.device.find(className="android.widget.TextView", + clickable=True, + textContains="Followers") + if tab == self.Tab.FOLLOWERS: + followers_tab.click() + else: + following_tab.click() + def scroll_to_bottom(self): print("Scroll to the bottom of the list") @@ -1333,6 +1353,50 @@ def getTimestamp(self): return None +class DialogView(InstagramView): + + UNFOLLOW_BUTTON_ID_REGEX = '{0}:id/follow_sheet_unfollow_row|{1}:id/button_positive|{2}:id/primary_button' + UNFOLLOW_BUTTON_CLASS_NAME_REGEX = 'android.widget.TextView|android.widget.Button' + UNFOLLOW_BUTTON_TEXT_REGEX = case_insensitive_re("Unfollow") + LOCATION_DENY_BUTTON_ID_REGEX = '.*?:id/permission_deny.*?' + LOCATION_DENY_BUTTON_CLASS_NAME_REGEX = 'android.widget.TextView|android.widget.Button' + LOCATION_CHECKBOX_ID_REGEX = '.*?:id/do_not_ask_checkbox' + + def is_visible(self) -> bool: + dialog_v1 = self.device.find(resourceId=f'{self.device.app_id}:id/dialog_root_view', + className='android.widget.FrameLayout') + dialog_v2 = self.device.find(resourceId=f'{self.device.app_id}:id/dialog_container', + className='android.view.ViewGroup') + dialog_v3 = self.device.find(resourceId=f'{self.device.app_id}:id/content', + className='android.widget.FrameLayout') + dialog_v4 = self.device.find(resourceIdMatches='com.android.(permissioncontroller|packageinstaller):id/.*?', + className='android.widget.LinearLayout') + return dialog_v1.exists(quick=True) or dialog_v2.exists(quick=True) or dialog_v3.exists(quick=True) or dialog_v4.exists(quick=True) + + def click_unfollow(self) -> bool: + unfollow_button = self.device.find( + resourceIdMatches=self.UNFOLLOW_BUTTON_ID_REGEX.format(self.device.app_id, self.device.app_id, self.device.app_id), + classNameMatches=self.UNFOLLOW_BUTTON_CLASS_NAME_REGEX, + textMatches=self.UNFOLLOW_BUTTON_TEXT_REGEX + ) + if unfollow_button.exists(): + unfollow_button.click() + return True + return False + + def click_deny_location_access(self) -> bool: + + deny_button = self.device.find(resourceIdMatches=self.LOCATION_DENY_BUTTON_ID_REGEX, + classNameMatches=self.LOCATION_DENY_BUTTON_CLASS_NAME_REGEX) + checkbox = self.device.find(resourceIdMatches=self.LOCATION_CHECKBOX_ID_REGEX, + className="android.widget.CheckBox") + checkbox.click(ignore_if_missing=True) + if deny_button.exists(): + deny_button.click() + return True + return False + + class LanguageNotEnglishException(Exception): pass diff --git a/registration/api.py b/registration/api.py index a6027a3..4c8269e 100644 --- a/registration/api.py +++ b/registration/api.py @@ -11,6 +11,8 @@ CONFIRMATION_CODE_MAX_ATTEMPTS_COUNT = 5 # times to retry requesting of confirmation code SMSPVA_COUNTRY_CODE = "RU" # or "US" or "ID" or any other else SMSPVA_API_KEY = "your-api-key" # more info on the official smspva site http://smspva.com/new_theme_api.html +SMS_ACTIVATE_COUNTRY_CODE = 0 # 0 is Russia, other codes are listed in the docs https://sms-activate.ru/en/api2#number +SMS_ACTIVATE_API_KEY = "your-api-key" # more info on the official sms-activate.ru site https://sms-activate.ru/en/ class PhoneNumberData: @@ -24,6 +26,8 @@ def __init__(self, response_id, country_code, phone_number): self.phone_number = phone_number +# -------- Simple implementation -------- + def _get_phone_number_simple() -> Optional[PhoneNumberData]: data = PhoneNumberData(0, None, None) while data.country_code is None or data.phone_number is None: @@ -41,6 +45,8 @@ def _get_confirmation_code_simple(_) -> Optional[str]: return input("Enter confirmation code: ") +# -------- smspva.com API -------- + def _get_phone_number_smspva() -> Optional[PhoneNumberData]: url = f"http://smspva.com/priemnik.php?metod=get_number&service=opt16" \ f"&country={SMSPVA_COUNTRY_CODE}" \ @@ -86,11 +92,73 @@ def _get_confirmation_code_smspva(response_id) -> Optional[str]: return confirmation_code +# -------- sms-activate.ru API -------- + +def _get_phone_number_sms_activate() -> Optional[PhoneNumberData]: + try: + import phonenumbers + except ImportError: + print(COLOR_FAIL + f"Using sms-activate.ru API requires phonenumbers library." + COLOR_ENDC) + print(COLOR_FAIL + COLOR_BOLD + f"python3 -m pip install phonenumbers" + COLOR_ENDC) + return None + url = f"https://sms-activate.ru/stubs/handler_api.php" \ + f"?api_key={SMS_ACTIVATE_API_KEY}" \ + f"&country={SMS_ACTIVATE_COUNTRY_CODE}" \ + f"&action=getNumber" \ + f"&service=ig" + code, body, fail_reason = network.get(url, 'Mozilla/5.0') + if code != HTTP_OK or body is None: + print(COLOR_FAIL + f"Cannot get phone number via sms-activate.ru API: {code} ({fail_reason})" + COLOR_ENDC) + return None + + response_regex = re.compile(r'ACCESS_NUMBER:(\d+):(\d+)') + match = response_regex.match(body.decode('utf-8')) + if match: + response_id = match.group(1) + full_phone_number = match.group(2) + else: + print(COLOR_FAIL + f"Cannot parse sms-activate.ru response: {str(body)})" + COLOR_ENDC) + return None + + phone_number_object = phonenumbers.parse(f'+{full_phone_number}', None) + country_code = str(phone_number_object.country_code) + phone_number = str(phone_number_object.national_number) + phone_number_data = PhoneNumberData(response_id, country_code, phone_number) + return phone_number_data + + +def _get_confirmation_code_sms_activate(response_id) -> Optional[str]: + url = f"https://sms-activate.ru/stubs/handler_api.php" \ + f"?api_key={SMS_ACTIVATE_API_KEY}" \ + f"&id={response_id}" \ + f"&country={SMSPVA_COUNTRY_CODE}" \ + f"&action=getStatus" + attempts_count = 0 + while True: + sleeper.random_sleep(multiplier=8.0) + code, body, fail_reason = network.get(url, 'Mozilla/5.0') + attempts_count += 1 + if code != HTTP_OK or body is None: + print(COLOR_FAIL + f"Cannot get confirmation code via sms-activate.ru API: {code} ({fail_reason})" + COLOR_ENDC) + return None + + response_regex = re.compile(r'STATUS_OK:(\d+)') + match = response_regex.match(body.decode('utf-8')) + if match: + confirmation_code = match.group(1) + return confirmation_code + else: + if attempts_count >= CONFIRMATION_CODE_MAX_ATTEMPTS_COUNT: + print("Well, looks like Instagram isn't going to send SMS to this phone number") + return None + print("Let's wait a bit more: confirmation code isn't received yet") + + # Choose either "simple" implementation (asks you to enter phone number and confirmation code in the terminal manually) # or implementation via smspva.com API (automatically gets confirmation code from a remote SIM card). # # You can also write your own implementation! It just has to follow these rules: 1) get_phone_number() returns # PhoneNumberData object and 2) get_confirmation_code(response_id) takes response_id argument from PhoneNumberData and # returns confirmation code (string). -get_phone_number = _get_phone_number_simple # _get_phone_number_smspva -get_confirmation_code = _get_confirmation_code_simple # _get_confirmation_code_smspva +get_phone_number = _get_phone_number_simple # or _get_phone_number_smspva or _get_phone_number_sms_activate +get_confirmation_code = _get_confirmation_code_simple # or _get_confirmation_code_smspva or _get_confirmation_code_sms_activate