Skip to content

Commit

Permalink
v3.0 (Update 5)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidwickerhf committed Dec 21, 2020
1 parent 9ec3ea1 commit d508c8e
Show file tree
Hide file tree
Showing 14 changed files with 154 additions and 98 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ client = InstaClient(driver_path='<projectfolder>/chromedriver.exe')
from instaclient.errors import *

try:
client.login(username=username, password=password) # Go through Login Procedure
client_login(username=username, password=password) # Go through Login Procedure
except VerificationCodeNecessary:
# This error is raised if the user has 2FA turned on.
code = input('Enter the 2FA security code generated by your Authenticator App or sent to you by SMS')
Expand Down
34 changes: 16 additions & 18 deletions instaclient/client/auth.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from instaclient.client import *
from instaclient.client.checker import Checker
from instaclient.client.component import Component

if TYPE_CHECKING:
from instaclient.client.instaclient import InstaClient


class Auth(Component):
class Auth(Checker):
@Component._manage_driver(login=False)
def login(self:'InstaClient', username:str, password:str, check_user:bool=True, _discard_driver:bool=False):
def _login(self:'InstaClient', username:str, password:str, check_user:bool=True, _discard_driver:bool=False):
"""
Sign Into Instagram with credentials. Go through 2FA if necessary. Sets the InstaClient variable `InstaClient.logged_in` to True if login was successful.
Expand All @@ -32,22 +33,21 @@ def login(self:'InstaClient', username:str, password:str, check_user:bool=True,
# Attempt Login
self.driver.get(ClientUrls.LOGIN_URL)
LOGGER.debug('INSTACLIENT: Got Login Page')
# Detect Cookies Dialogue

if self._check_existence( EC.presence_of_element_located((By.XPATH, Paths.COOKIES_LINK))):
self._dismiss_cookies()
# Detect Cookies Dialogue
self._dismiss_cookies()

# Get Form elements
username_input = self._check_existence( EC.presence_of_element_located((By.XPATH,Paths.USERNAME_INPUT)), url=ClientUrls.LOGIN_URL)
password_input = self._check_existence( EC.presence_of_element_located((By.XPATH,Paths.PASSWORD_INPUT)), url=ClientUrls.LOGIN_URL)
username_input = self._find_element( EC.presence_of_element_located((By.XPATH,Paths.USERNAME_INPUT)), url=ClientUrls.LOGIN_URL)
password_input = self._find_element( EC.presence_of_element_located((By.XPATH,Paths.PASSWORD_INPUT)), url=ClientUrls.LOGIN_URL)
LOGGER.debug('INSTACLIENT: Found elements')
# Fill out form
username_input.send_keys(username)
time.sleep(1)
password_input.send_keys(password)
time.sleep(1)
LOGGER.debug('INSTACLIENT: Filled in form')
login_btn = self._check_existence( EC.presence_of_element_located((By.XPATH,Paths.LOGIN_BTN)), url=ClientUrls.LOGIN_URL)# login button xpath changes after text is entered, find first
login_btn = self._find_element( EC.presence_of_element_located((By.XPATH,Paths.LOGIN_BTN)), url=ClientUrls.LOGIN_URL)# login button xpath changes after text is entered, find first
self._press_button(login_btn)
LOGGER.debug('INSTACLIENT: Sent form')
except ElementClickInterceptedException as error:
Expand Down Expand Up @@ -117,17 +117,15 @@ def login(self:'InstaClient', username:str, password:str, check_user:bool=True,
self.driver.get(ClientUrls.HOME_URL)

# Detect 'Save to Home Screen' Dialogue
if self._check_existence(EC.presence_of_element_located((By.XPATH, Paths.DISMISS_DIALOGUE))):
self._dismiss_dialogue()
self._dismiss_dialogue()

# Detect 'Turn On Notifications' Box
if self._check_existence(EC.presence_of_element_located((By.XPATH, Paths.DISMISS_DIALOGUE))):
self._dismiss_dialogue()
self._dismiss_dialogue()
return self.logged_in


@Component._manage_driver(login=False)
def resend_security_code(self):
def _resend_security_code(self):
"""
Resend security code if code hasn't been sent successfully. The code is used to verify the login attempt if `instaclient.errors.common.SuspiciousLoginAttemptError` is raised.
Expand Down Expand Up @@ -161,9 +159,9 @@ def resend_security_code(self):


@Component._manage_driver(login=False)
def input_security_code(self, code:int or str, _discard_driver:bool=False):
def _input_security_code(self:'InstaClient', code:int, _discard_driver:bool=False):
"""
Complete login procedure started with `InstaClient.login()` and insert security code required if `instaclient.errors.common.SuspiciousLoginAttemptError` is raised. Sets `InstaClient.logged_in` attribute to True if login was successful.
Complete login procedure started with `InstaClient_login()` and insert security code required if `instaclient.errors.common.SuspiciousLoginAttemptError` is raised. Sets `InstaClient.logged_in` attribute to True if login was successful.
Args:
code (intorstr): The security code sent by Instagram via SMS or email.
Expand Down Expand Up @@ -199,9 +197,9 @@ def input_security_code(self, code:int or str, _discard_driver:bool=False):


@Component._manage_driver(login=False)
def input_verification_code(self, code:int or str, _discard_driver:bool=False):
def _input_verification_code(self, code:int, _discard_driver:bool=False):
"""
Complete login procedure started with `InstaClient.login()` and insert 2FA security code. Sets `instaclient.logged_in` to True if login was successful.
Complete login procedure started with `InstaClient_login()` and insert 2FA security code. Sets `instaclient.logged_in` to True if login was successful.
Args:
code (int|str): The 2FA security code generated by the Authenticator App or sent via SMS to the user.
Expand Down Expand Up @@ -232,7 +230,7 @@ def input_verification_code(self, code:int or str, _discard_driver:bool=False):


@Component._manage_driver(login=False)
def logout(self, _discard_driver:bool=False):
def _logout(self:'InstaClient', _discard_driver:bool=False):
"""
Check if the client is currently connected to Instagram and logs of the current InstaClient session.
Expand Down
8 changes: 3 additions & 5 deletions instaclient/client/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@ def _check_status(self: 'InstaClient', _discard_driver:bool=False):
LOGGER.debug(self.driver.current_url)
if ClientUrls.HOME_URL not in self.driver.current_url:
self.driver.get(ClientUrls.HOME_URL)
if self._check_existence( EC.presence_of_element_located((By.XPATH, Paths.COOKIES_LINK))):
self._dismiss_cookies()
self._dismiss_cookies()
if self._check_existence( EC.presence_of_element_located((By.XPATH, Paths.NOT_NOW_BTN))):
btn = self._check_existence( EC.presence_of_element_located((By.XPATH, Paths.NOT_NOW_BTN)))
btn = self._find_element( EC.presence_of_element_located((By.XPATH, Paths.NOT_NOW_BTN)))
self._press_button(btn)
LOGGER.debug('INSTACLIENT: Dismissed dialogue')

Expand Down Expand Up @@ -81,8 +80,7 @@ def _is_valid_user(self:'InstaClient', user:str, nav_to_user:bool=True, _discard
time.sleep(1)


if self._check_existence(EC.presence_of_element_located((By.XPATH, Paths.COOKIES_LINK))):
self._dismiss_cookies()
self._dismiss_cookies()

element = self._check_existence(EC.presence_of_element_located((By.XPATH, Paths.PAGE_NOT_FOUND)), wait_time=3)
if element:
Expand Down
13 changes: 4 additions & 9 deletions instaclient/client/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def wrapper(self: 'InstaClient', *args, **kwargs):
error = False
result = None
try:
result = func(*args, **kwargs)
result = func(self, *args, **kwargs)
time.sleep(1)
except Exception as exception:
error = exception
Expand All @@ -41,7 +41,6 @@ def wrapper(self: 'InstaClient', *args, **kwargs):
return outer


@staticmethod
def _discard_driver(self: 'InstaClient'):
LOGGER.debug('INSTACLIENT: Discarding driver...')
if self.driver:
Expand All @@ -51,7 +50,6 @@ def _discard_driver(self: 'InstaClient'):
LOGGER.debug('INSTACLIENT: Driver Discarded')


@staticmethod
def _init_driver(self: 'InstaClient', login=False, retries=0, func=None):
LOGGER.debug('INSTACLIENT: Initiating Driver | attempt {} | func: {}'.format(retries, func))
try:
Expand Down Expand Up @@ -99,7 +97,7 @@ def _init_driver(self: 'InstaClient', login=False, retries=0, func=None):

if login:
try:
self.login(self.username, self.password)
self._login(self.username, self.password)
except:
raise InstaClientError(message='Tried logging in when initiating driver, but username and password are not defined.')

Expand Down Expand Up @@ -139,11 +137,9 @@ def _find_element(self:'InstaClient', expectation, url:str=None, wait_time:int=5
LOGGER.debug('Retrying find element...')
if attempt == 0:
LOGGER.debug('Checking for cookies/dialogues...')
if self._check_existence(EC.presence_of_element_located((By.XPATH, Paths.COOKIES_LINK))):
self._dismiss_cookies()
self._dismiss_cookies()

if self._check_existence(EC.presence_of_element_located((By.XPATH, Paths.DISMISS_DIALOGUE))):
self._dismiss_dialogue()
self._dismiss_dialogue()
return self._find_element(expectation, url, wait_time=2, attempt=attempt+1)
elif retry:
LOGGER.debug('Checking if user is logged in...')
Expand Down Expand Up @@ -172,7 +168,6 @@ def _find_element(self:'InstaClient', expectation, url:str=None, wait_time:int=5
raise NoSuchElementException()


@staticmethod
def _check_existence(self:'InstaClient', expectation, wait_time:int=2):
"""
Checks if an element exists.
Expand Down
58 changes: 56 additions & 2 deletions instaclient/client/instaclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,68 @@ def __init__(self, driver_type: int=CHROMEDRIVER, host_type:int=LOCAHOST, driver
if init_driver:
self._init_driver(func='__init__')


# AUTH
def login(self: 'InstaClient', username: str, password: str, check_user: bool=True, _discard_driver: bool=False):
return super()._login(username, password, check_user=check_user, _discard_driver=_discard_driver)

def resend_security_code(self):
return super()._resend_security_code()

def input_security_code(self, code: int, _discard_driver: bool=False):
return super()._input_security_code(code, _discard_driver=_discard_driver)

def input_verification_code(self, code: int, _discard_driver: bool=False):
return super()._input_verification_code(code, _discard_driver=_discard_driver)

def logout(self: 'InstaClient', _discard_driver: bool=False):
return super()._logout(_discard_driver=_discard_driver)



# CHECKERS
def check_status(self: 'InstaClient', _discard_driver: bool=False) -> bool:
return super()._check_status(_discard_driver=_discard_driver)

def is_valid_user(self: 'InstaClient', user: str, nav_to_user: bool=True, _discard_driver: bool=False) -> bool:
return super()._is_valid_user(user, nav_to_user=nav_to_user, _discard_driver=_discard_driver)


# SCRAPING
# SCRAPING
def get_notifications(self: 'InstaClient', types: Optional[list]=None, count: Optional[int]=None) -> Optional[list]:
return super()._scrape_notifications(types=types, count=count)

def get_profile(self: 'InstaClient', username: str, context: bool=True) -> Optional[Profile]:
return super()._scrape_profile(username, context=context)

def get_user_images(self, user: str, _discard_driver: bool=False):
return super()._scrape_user_images(user, _discard_driver=_discard_driver)

def get_followers(self, user: str, count: int, check_user:bool=True, _discard_driver:bool=False, callback_frequency: int=100, callback=None, **callback_args) -> Optional[list]:
return super()._scrape_followers(user, count, check_user=check_user, _discard_driver=_discard_driver, callback_frequency=callback_frequency, callback=callback, **callback_args)

def get_hashtag(self: 'InstaClient', tag: str) -> Optional[Hashtag]:
return super()._scrape_tag(tag, self.username)



# INTERACTIONS
def follow(self, user: str, nav_to_user: bool=True, _discard_driver: bool=False):
return super()._follow_user(user, nav_to_user=nav_to_user, _discard_driver=_discard_driver)

def unfollow(self, user: str, nav_to_user:bool=True, check_user:bool=True, _discard_driver: bool=False):
return super()._unfollow_user(user, nav_to_user=nav_to_user, check_user=check_user, _discard_driver=_discard_driver)

def send_dm(self, user: str, message: str, _discard_driver: bool=False):
return super()._send_dm(user, message, _discard_driver=_discard_driver)

def like_user_posts(self, user: str, n_posts: int, like: bool=True, _discard_driver: bool=False):
return super()._like_latest_posts(user, n_posts, like=like, _discard_driver=_discard_driver)

def like_feed_posts(self, count:int):
return super()._like_feed_posts(count)

def scroll(self, mode:int=Interactions.PAGE_DOWN_SCROLL, size:int=500, times:int=1, interval:int=3):
return super()._scroll(mode=mode, size=size, times=times, interval=interval)

# INTERACTIONS

11 changes: 6 additions & 5 deletions instaclient/client/interactions.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from instaclient.client import *
from instaclient.client.component import Component
from instaclient.client.navigator import Navigator


class Interactions(Component):
class Interactions(Navigator):
PIXEL_SCROLL=3
END_PAGE_SCROLL=4
PAGE_DOWN_SCROLL=5
Expand All @@ -23,7 +24,7 @@ def _follow_user(self, user:str, nav_to_user:bool=True, _discard_driver:bool=Fal
"""
# Navigate to User Page
if nav_to_user:
self.nav_user(user, check_user=False)
self._nav_user(user, check_user=False)

# Check User Vadility
try:
Expand Down Expand Up @@ -64,7 +65,7 @@ def _unfollow_user(self, user:str, nav_to_user=True, check_user=True, _discard_d
InvalidUserError: raised if the user specified by the `user` argument is invalid.
"""
if nav_to_user:
self.nav_user(user, check_user)
self._nav_user(user, check_user)
elif check_user:
try:
self._is_valid_user(user, nav_to_user=False)
Expand Down Expand Up @@ -101,7 +102,7 @@ def _like_latest_posts(self, user:str, n_posts:int, like:bool=True, _discard_dri

action = 'Like' if like else 'Unlike'

self.nav_user(user)
self._nav_user(user)

imgs = []
elements = self._find_element(EC.presence_of_all_elements_located((By.CLASS_NAME, '_9AhH0')))
Expand Down Expand Up @@ -133,7 +134,7 @@ def _send_dm(self, user:str, message:str, _discard_driver:bool=False):
"""
# Navigate to User's dm page
try:
self.nav_user_dm(user)
self._nav_user_dm(user)
text_area = self._find_element(EC.presence_of_element_located((By.XPATH, Paths.DM_TEXT_AREA)))
text_area.send_keys(message)
time.sleep(1)
Expand Down
2 changes: 1 addition & 1 deletion instaclient/client/navigator.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def _nav_user_dm(self:'InstaClient', user:str, check_user:bool=True):
True if operation was successful
"""
try:
self.nav_user(user, check_user=check_user)
self._nav_user(user, check_user=check_user)
private = False
LOGGER.debug('INSTACLIENT: User <{}> is valid and public (or followed)'.format(user))
except PrivateAccountError:
Expand Down
Loading

0 comments on commit d508c8e

Please sign in to comment.