<gh_stars>0
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Author: <NAME>

import os, random, sys, time, datetime, csv, re
from configure import *
import urllib.parse as urlparse
from selenium import webdriver
from bs4 import BeautifulSoup
from random import shuffle


from os.path import join, dirname
# from dotenv import load_dotenv

# dotenv_path = join(dirname(__file__), '.env')
# load_dotenv(dotenv_path)


# Configurable Constants
EMAIL = '<EMAIL>'
PASSWORD ='<PASSWORD>'
VIEW_SPECIFIC_USERS = False
SPECIFIC_USERS_TO_VIEW = ['CEO', 'CTO', 'Developer', 'HR', 'Recruiter']
NUM_LAZY_LOAD_ON_MY_NETWORK_PAGE = 5
CONNECT_WITH_USERS = True
RANDOMIZE_CONNECTING_WITH_USERS = True
JOBS_TO_CONNECT_WITH = ['CEO', 'CTO', 'Developer', 'HR', 'Recruiter']
ENDORSE_CONNECTIONS = False
RANDOMIZE_ENDORSING_CONNECTIONS = True
VERBOSE = True
SESSION_CONNECTION_COUNT = 0
TEMP_NAME=""
TEMP_JOB=""
TEMP_JOBMATCH=""
TEMP_LOCATION=""
TEMP_LOCATIONMATCH=""
TEMP_PROFILE=[]
CONNECTED = False
TIME = str(time.time_ns())
CSV_DATA = [["Name","Title", "Title Match", "Location","Location Match", "Current Company","Connected"]]


def Launch():
    """
    Launch the LinkedIn bot.
    """

    # Check if the file 'visitedUsers.txt' exists, otherwise create it
    if os.path.isfile('visitedUsers.txt') == False:
        visitedUsersFile = open('visitedUsers.txt', 'wb')
        visitedUsersFile.close()

    # Browser choice
    print('Choose your browser:')
    print('[1] Chrome')
    print('[2] Firefox/Iceweasel')
    print('[3] PhantomJS')

    while True:
        try:
            browserChoice = int(1)
            # browserChoice = int(input('Choice? '))
        except ValueError:
            print('Invalid choice.'),
        else:
            if browserChoice not in [1,2,3]:
                print('Invalid choice.'),
            else:
                break

    StartBrowser(browserChoice)

def StartBrowser(browserChoice):
    """
    Launch broswer based on the user's selected choice.
    browserChoice: the browser selected by the user.
    """

    if browserChoice == 1:
        print('\nLaunching Chrome')
        browser = webdriver.Chrome()

    elif browserChoice == 2:
        print('\nLaunching Firefox/Iceweasel')
        browser = webdriver.Firefox()

    elif browserChoice == 3:
        print('\nLaunching PhantomJS')
        browser = webdriver.PhantomJS()

    # Sign in
    browser.get('https://linkedin.com/uas/login')
    emailElement = browser.find_element_by_id('username')
    emailElement.send_keys(EMAIL)
    passElement = browser.find_element_by_id('password')
    passElement.send_keys(PASSWORD)
    passElement.submit()

    print('Signing in...')
    time.sleep(3)

    soup = BeautifulSoup(browser.page_source, "lxml")
    if soup.find('div', {'class':'alert error'}):
        print('Error! Please verify your username and password.')
        browser.quit()
    elif browser.title == '403: Forbidden':
        print('LinkedIn is momentarily unavailable. Please wait a moment, then try again.')
        browser.quit()
    else:
        print('Success!\n')
        LinkedInBot(browser)


def LinkedInBot(browser):
    """
    Run the LinkedIn Bot.
    browser: the selenium driver to run the bot with.
    """

    T = 0
    V = 0
    profilesQueued = []
    error403Count = 0
    timer = time.time()

    if ENDORSE_CONNECTIONS:
        EndorseConnections(browser)

    if SCREENSHOTS:
        if PRINT_ACTIONS:
	        print("-> Enabled Screenshots")
        try:
            os.makedirs("Screenshots")
            if PRINT_ACTIONS:
                print("Created Screenshot Folder")
        except FileExistsError:
            pass
    header = ["Name","Title", "Title Match", "Location","Location Match", "Current Company", "Connected"]
    if SAVECSV:
        if PRINT_ACTIONS:
            print("-> Enabled Save as CSV")
        try:
            os.makedirs("CSV")
            if PRINT_ACTIONS:
                print("\t* Created CSV Folder")
        except FileExistsError:
            pass
        createCSV(header, TIME)
    if PRINT_ACTIONS:
        print('-> Scraping User URLs on Network tab.')

    # Infinite loop
    while True:

        # Generate random IDs
        while True:

            NavigateToMyNetworkPage(browser)
            T += 1

            if GetNewProfileURLS(BeautifulSoup(browser.page_source, "lxml"), profilesQueued):
                break
            else:
                print('|'),
                time.sleep(random.uniform(5, 7))

        soup = BeautifulSoup(browser.page_source, "lxml")
        profilesQueued = list(set(GetNewProfileURLS(soup, profilesQueued)))

        V += 1
        # print('\n\nGot our users to start viewing with!\n')
        # print(browser.title.replace(' | LinkedIn', ''), ' visited. T:', T, '| V:', V, '| Q:', len(profilesQueued))

        print('\t* Finished gathering User URLs.\n')
        print("--> Starting Process\n")

        while profilesQueued:

            shuffle(profilesQueued)
            profileID = profilesQueued.pop()
            browser.get('https://www.linkedin.com'+profileID)

            # Connect with users if the flag is turned on and matches your criteria
            if CONNECT_WITH_USERS:
                if not RANDOMIZE_CONNECTING_WITH_USERS:
                    ConnectWithUser(browser)
                elif random.choice([True, False]):
                    ConnectWithUser(browser)

            # Add the ID to the visitedUsersFile

            title_check = False
            title_check = TitleCheck(browser)
            location_check = LocationCheck(browser)

            if(CONNECT_BY_LOCATION):
                locationMatches = LocationCheck(browser)
                if VERBOSE:
                    print("Location Matches: "+str(locationMatches))
            else:
                locationMatches = False
            regex = r'\(.*?\)'
            TEMP_NAME = re.sub(regex, '', browser.title.replace(' | LinkedIn', ''))
            TEMP_JOB = JobMatch(browser)
            TEMP_LOCATION = LocationMatch(browser)
            #company = getCompany(browser)
            company ="n/a"
            title ="n/a"

            if " at " in TEMP_JOBMATCH:
                company = TEMP_JOBMATCH.split(" at ",1)[1]
            elif " for " in TEMP_JOBMATCH:
                company = TEMP_JOBMATCH.split(" for ",1)[1]
            if " at " in TEMP_JOBMATCH:
                title = TEMP_JOBMATCH.split(" at ",1)[0]
            elif " for " in TEMP_JOBMATCH:
                title = TEMP_JOBMATCH.split(" for ",1)[0]
                
            if POTENTIAL_COMPANY:
                if company == "n/a":
                    company = getCompany(browser)
            
            if VIEW_MODE.upper() == "BOX":
                tvq = str(T)+":"+str(V)+":"+str(len(profilesQueued))
                print("┌───────────────────────────────────┐")
                print("│● Name: %-26.26s │"%(TEMP_NAME))
                if CONNECT_BY_LOCATION and VIEW_SPECIFIC_TITLES:
                    print("├───────────────────────────────────┼───────────────────────────────────┬───────────────────────────────────┐")
                    print("│ Title Match: %-20.20s │ Location Match: %-17.17s │ T:V:Q %-28s│" %(TEMP_JOB, TEMP_LOCATION, tvq))
                elif CONNECT_BY_LOCATION:
                    print("│ Title Match: %-20.20s │ Location Match: %-17.17s │ T:V:Q %-28s│" %("OFF", TEMP_LOCATION, tvq))
                elif VIEW_SPECIFIC_TITLES:
                    print("│ Title Match: %-20.20s │ Location Match: %-17.17s │ T:V:Q %-28s│" %(TEMP_JOB, "OFF", tvq))
                else:
                    print("│ Title Match: %-20.20s │ Location Match: %-17.17s │ T:V:Q %-28s│" %("OFF", "OFF", tvq))
                if EXTRA_USER_INFO:
                    print("├───────────────────────────────────┼───────────────────────────────────┼───────────────────────────────────┤")
                    print("│ Title: %-26.26s │ Location: %-23.23s │ Company %-26.26s│" %(title,TEMP_LOCATIONMATCH, company))
                print("└───────────────────────────────────┴───────────────────────────────────┴───────────────────────────────────┘")
            elif VIEW_MODE.upper() == "COMPRESSED":
                if CONNECT_BY_LOCATION and VIEW_SPECIFIC_TITLES:
                    print("● Name: %-17.17s | Title Match: %-15.15s | Location Match: %-13.13s | T:V:Q %-3.3d:%-3.3d:%-3.3d" %(TEMP_NAME, TEMP_JOB, TEMP_LOCATION, T, V, len(profilesQueued)))
                elif CONNECT_BY_LOCATION:
                    print("● Name: %-17.17s | Location Match: %-13.13s | T:V:Q %-3.3d:%-3.3d:%-3.3d" %(TEMP_NAME, TEMP_LOCATION, T, V, len(profilesQueued)))
                elif VIEW_SPECIFIC_TITLES:
                    print("● Name: %-17.17s | Title Match: %-15.15s | T:V:Q %-3.3d:%-3.3d:%-3.3d" %(TEMP_NAME, TEMP_JOB, T, V, len(profilesQueued)))
                else:
                    print("● Name: %-17.17s | T:V:Q %-3.3d:%-3.3d:%-3.3d" %(TEMP_NAME, T, V, len(profilesQueued)))
                if EXTRA_USER_INFO:
                    print("↳       %17.17s | Title: %-21.21s | Location: %-19.19s | Company %-15.15s" %("",title,TEMP_LOCATIONMATCH, company))
            else:
                if CONNECT_BY_LOCATION and VIEW_SPECIFIC_TITLES:
                    print("● Name: %-17.17s | Title Match: %-15.15s | Location Match: %-13.13s | T:V:Q %-3.3d:%-3.3d:%-3.3d" %(TEMP_NAME, TEMP_JOB, TEMP_LOCATION, T, V, len(profilesQueued)))
                elif CONNECT_BY_LOCATION:
                    print("● Name: %-17.17s | Location Match: %-13.13s | T:V:Q %-3.3d:%-3.3d:%-3.3d" %(TEMP_NAME, TEMP_LOCATION, T, V, len(profilesQueued)))
                elif VIEW_SPECIFIC_TITLES:
                    print("● Name: %-17.17s | Title Match: %-15.15s | T:V:Q %-3.3d:%-3.3d:%-3.3d" %(TEMP_NAME, TEMP_JOB, T, V, len(profilesQueued)))
                else:
                    print("● Name: %-17.17s | T:V:Q %-3.3d:%-3.3d:%-3.3d" %(TEMP_NAME, T, V, len(profilesQueued)))
                if EXTRA_USER_INFO:
                    print("↳       %17.17s | Title: %-21.21s | Location: %-19.19s | Company %-15.15s" %("",title,TEMP_LOCATIONMATCH, company))
            
            if CONNECT_WITH_USERS and CONNECT_BY_LOCATION:
                if location_check:
                    if LIMIT_CONNECTION and (SESSION_CONNECTION_COUNT<CONNECTION_LIMIT):
                        if not RANDOMIZE_CONNECTING_WITH_USERS:
                            ConnectWithUser(browser)
                        elif random.choice([True, False]):
                            ConnectWithUser(browser)
                    elif not LIMIT_CONNECTION:
                        if not RANDOMIZE_CONNECTING_WITH_USERS:
                            ConnectWithUser(browser)
                        elif random.choice([True, False]):
                            ConnectWithUser(browser)
            elif CONNECT_WITH_USERS:
                if LIMIT_CONNECTION and (SESSION_CONNECTION_COUNT<CONNECTION_LIMIT):
                    if not RANDOMIZE_CONNECTING_WITH_USERS:
                        ConnectWithUser(browser)
                    elif random.choice([True, False]):
                        ConnectWithUser(browser)
                elif not LIMIT_CONNECTION:
                    if not RANDOMIZE_CONNECTING_WITH_USERS:
                        ConnectWithUser(browser)
                    elif random.choice([True, False]):
                        ConnectWithUser(browser)

            TEMP_PROFILE = [TEMP_NAME, TEMP_JOB, TEMP_JOBMATCH, TEMP_LOCATION, TEMP_LOCATIONMATCH, company, CONNECTED]
            if SAVECSV:
                addToCSV(TEMP_PROFILE, TIME)
                if VERBOSE:
                    print("-> Temp Profile List")
                    print(TEMP_PROFILE)

                        
            with open('visitedUsers.txt', 'a') as visitedUsersFile:
                visitedUsersFile.write(profileID+'\r\n')
            visitedUsersFile.close()

            # Get new profiles ID
            time.sleep(10)
            soup = BeautifulSoup(browser.page_source, PARSER)
            profilesQueued.extend(GetNewProfileURLS(soup, profilesQueued))
            profilesQueued = list(set(profilesQueued))

            browserTitle = (browser.title).replace('  ',' ')

            

            # 403 error
            if browserTitle == '403: Forbidden':
                error403Count += 1
                print('\nLinkedIn is momentarily unavailable - Paused for', str(error403Count), 'hour(s)\n')
                time.sleep(3600*error403Count+(random.randrange(0, 10))*60)
                timer = time.time() # Reset the timer

            # User out of network
            elif browserTitle == 'Profile | LinkedIn':
                T += 1
                error403Count = 0
                print('User not in your network. T:', T, '| V:', V, '| Q:', len(profilesQueued))

            # User in network
            else:
                T += 1
                V += 1
                error403Count = 0
                print(browserTitle.replace(' | LinkedIn', ''), 'visited. T:', T, '| V:', V, '| Q:', len(profilesQueued))

            # Pause
            if (T%1000==0) or time.time()-timer > 3600:
                print('\nPaused for 1 hour\n')
                time.sleep(3600+(random.randrange(0, 10))*60)
                timer = time.time() # Reset the timer
            else:
                time.sleep(random.uniform(5, 7)) # Otherwise, sleep to make sure everything loads

        print('\nNo more profiles to visit. Everything restarts with the network page...\n')


def LocationCheck(browser):
	'''
	Checks if the location of the user matches your list of locations
	browser = selenium webdriver
	returns true or false
	'''
	soup = BeautifulSoup(browser.page_source, PARSER)
	
	ul = soup.find("ul", {"class": "pv-top-card--list"})
	ul = ul.find_next("ul", {"class": "pv-top-card--list"})
	li = ul.find_next("li")
	for l in LOCATIONS_TO_CONNECT:
		if VERBOSE:
			print(">>>> Web Location: "+str(li.text)+" Settings Location: "+str(l))
		if l.lower() in li.text.lower():
			return True
		else:
			return False

def TitleCheck(browser):

    soup = BeautifulSoup(browser.page_source, PARSER)
    ul = soup.find("ul", {"class": "pv-top-card--list"})
    h2 = ul.find_next("h2")

    
    for job in TITLES_TO_VIEW_CONNECT_WITH:
        if VERBOSE:
            print(">>>> Web Job/Title: "+str(h2.text)+" Settings Job/Title: "+str(job))
        if job.lower() in h2.text.lower():
            return True
        else:
            return False

def LocationMatch(browser):
	'''
	Gets the location that matches your settings and also sets TEMP_LOCATIONMATCH
	browser = selenium webdriver
	returns the locaiton as a string
	'''
	global TEMP_LOCATIONMATCH
	TEMP_LOCATIONMATCH = "X" #if this doesnt change it, it could have the previous connections data
	soup = BeautifulSoup(browser.page_source, PARSER)
	rtn = ""
	ul = soup.find("ul", {"class": "pv-top-card--list"})
	ul = ul.find_next("ul", {"class": "pv-top-card--list"})
	li = ul.find_next("li")
	for l in LOCATIONS_TO_CONNECT:
		if l.lower() in li.text.lower():
			TEMP_LOCATIONMATCH = str(" ".join((li.text.lower()).split()))
			location = str(l)
			rtn = location
			if VERBOSE:
				print(">>>> Location Match: "+rtn)
				print(">>>>"+location + " : " + TEMP_LOCATIONMATCH)
	if rtn != "":
		return rtn
	else:
		return("X")

def JobMatch(browser):
	'''
	Gets the job that matches your settings and also sets TEMP_JOBMATCH
	browser = selenium webdriver
	returns the job as a string
	'''

	global TEMP_JOBMATCH
	TEMP_JOBMATCH = "X" #if this doesnt change it, it could have the previous connections data
	soup = BeautifulSoup(browser.page_source, PARSER)
	rtn = ""
	ul = soup.find("ul", {"class": "pv-top-card--list"})
	li = ul.find_next("h2")
	for job in TITLES_TO_VIEW_CONNECT_WITH:
		if job.lower() in li.text.lower():
			TEMP_JOBMATCH = (" ".join((li.text.lower()).split()))
			jobtitle = str(job)
			rtn = jobtitle
			if VERBOSE:
				print(">>>> Job Match: "+rtn)
				print(">>>>"+jobtitle + " : " + TEMP_JOBMATCH)
	if rtn != "":
		return rtn
	else:
		return("X")

def createCSV(data, time):
	'''
	Creates initial CSV file
	data is the list that will get added to the file(in this case its the headers)
	time is the time at creation of this file
	'''
	filename = 'Linked-In-'+ time +'.csv'
	with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'CSV', filename), 'w',newline='') as csvFile:
		writer = csv.writer(csvFile)
		writer.writerow(data)
	csvFile.close()

def addToCSV(data, time):

    filename = 'Linked-In-'+ time +'.csv'
    if 2 < len(data):
        with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'CSV', filename), 'a',encoding='utf8', newline='') as csvFile:
            writer = csv.writer(csvFile, dialect='excel')
            writer.writerow(data)
        csvFile.close()

def NavigateToMyNetworkPage(browser):
    """
    Navigate to the my network page and scroll to the bottom and let the lazy loading
    go to be able to grab more potential users in your network. It is reccommended to
    increase the NUM_LAZY_LOAD_ON_MY_NETWORK_PAGE value if you are using the variable
    SPECIFIC_USERS_TO_VIEW.
    browser: the selenium browser used to interact with the page.
    """

    browser.get('https://www.linkedin.com/mynetwork/')
    for counter in range(1,NUM_LAZY_LOAD_ON_MY_NETWORK_PAGE):
        ScrollToBottomAndWaitForLoad(browser)


def ConnectWithUser(browser):
    """
    Connect with the user viewing if their job title is found in your list of roles
    you want to connect with.
    browse: the selenium browser used to interact with the page.
    """

    soup = BeautifulSoup(browser.page_source, "lxml")
    jobTitleMatches = False

    # I know not that efficient of a loop but BeautifulSoup and Selenium are
    # giving me a hard time finding the specifc h2 element that contain's user's job title
    for h2 in soup.find_all('h2'):
        for job in JOBS_TO_CONNECT_WITH:
            if job in h2.getText():
                jobTitleMatches = True
                break

    if jobTitleMatches:
        try:
            if VERBOSE:
                print('Sending the user an invitation to connect.')
                browser.find_element_by_xpath('//button[@class="pv-s-profile-actions pv-s-profile-actions--connect ml2 artdeco-button artdeco-button--2 artdeco-button--primary ember-view"]').click() #old class = connect primary top-card-action ember-view
                time.sleep(random.randrange(3))
                browser.find_element_by_xpath('//button[@class="ml1 artdeco-button artdeco-button--3 artdeco-button--primary ember-view"]').click()
        except:
            pass


def GetNewProfileURLS(soup, profilesQueued):
    """
    Get new profile urls to add to the navigate queue.
    soup: beautiful soup instance of page's source code.
    profileQueued: current list of profile queues.
    """

    # Open, load and close
    with open('visitedUsers.txt', 'r') as visitedUsersFile:
        visitedUsers = [line.strip() for line in visitedUsersFile]
    visitedUsersFile.close()

    profileURLS = []
    profileURLS.extend(FindProfileURLsInNetworkPage(soup, profilesQueued, profileURLS, visitedUsers))
    profileURLS.extend(FindProfileURLsInPeopleAlsoViewed(soup, profilesQueued, profileURLS, visitedUsers))
    profileURLS.extend(FindProfileURLsInEither(soup, profilesQueued, profileURLS, visitedUsers))
    profileURLS = list(set(profileURLS))
    return profileURLS


def FindProfileURLsInNetworkPage(soup, profilesQueued, profileURLS, visitedUsers):
    """
    Get new profile urls to add to the navigate queue from the my network page.
    soup: beautiful soup instance of page's source code.
    profileQueued: current list of profile queues.
    profileURLS: profile urls already found this scrape.
    visitedUsers: user's profiles that we have already viewed.
    """

    newProfileURLS = []

    try:
        for a in soup.find_all('a', class_='discover-entity-type-card__link'):
            if ValidateURL(a['href'], profileURLS, profilesQueued, visitedUsers):

                if VIEW_SPECIFIC_USERS:
                    for span in a.find_all('span', class_='discover-person-card__occupation'):
                        for occupation in SPECIFIC_USERS_TO_VIEW:
                            if occupation.lower() in span.text.lower():
                                if VERBOSE:
                                    print(a['href'])
                                newProfileURLS.append(a['href'])
                                break

                else:
                    if VERBOSE:
                        print(a['href'])
                    newProfileURLS.append(a['href'])
    except:
        pass

    return newProfileURLS


def FindProfileURLsInPeopleAlsoViewed(soup, profilesQueued, profileURLS, visitedUsers):
    """
    Get new profile urls to add to the navigate queue from the people also viewed section.
    soup: beautiful soup instance of page's source code.
    profileQueued: current list of profile queues.
    profileURLS: profile urls already found this scrape.
    visitedUsers: user's profiles that we have already viewed.
    """

    newProfileURLS = []

    try:
        for a in soup.find_all('a', class_='pv-browsemap-section__member'):
            if ValidateURL(a['href'], profileURLS, profilesQueued, visitedUsers):

                if VIEW_SPECIFIC_USERS:
                    for div in a.find_all('div'):
                        for occupation in SPECIFIC_USERS_TO_VIEW:
                            if occupation.lower() in div.text.lower():
                                if VERBOSE:
                                    print(a['href'])
                                newProfileURLS.append(a['href'])
                                break

                else:
                    if VERBOSE:
                        print(a['href'])
                    newProfileURLS.append(a['href'])
    except:
        pass

    return newProfileURLS


def FindProfileURLsInEither(soup, profilesQueued, profileURLS, visitedUsers):
    """
    Get new profile urls to add to the navigate queue, some use different class
    names in the my network page and people also viewed section.
    soup: beautiful soup instance of page's source code.
    profileQueued: current list of profile queues.
    profileURLS: profile urls already found this scrape.
    visitedUsers: user's profiles that we have already viewed.
    """

    newProfileURLS = []

    try:
        for ul in soup.find_all('ul', class_='pv-profile-section__section-info'):
            for li in ul.find_all('li'):
                a = li.find('a')
                if ValidateURL(a['href'], profileURLS, profilesQueued, visitedUsers):

                    if VIEW_SPECIFIC_USERS:
                        for div in a.find_all('div'):
                            for occupatio in SPECIFIC_USERS_TO_VIEW:
                                if VERBOSE:
                                    print(">>>" + occupatio)
                                if occupatio.lower() in div.text.lower():
                                    if VERBOSE:
                                        print(a['href'])
                                    profileURLS.append(a['href'])
                                    break

                    else:
                        if VERBOSE:
                            print(a['href'])
                        profileURLS.append(a['href'])
    except:
        pass

    return newProfileURLS


def ValidateURL(url, profileURLS, profilesQueued, visitedUsers):
    """
    Validate the url passed meets requirement to be navigated to.
    profileURLS: list of urls already added within the GetNewProfileURLS method to be returned.
        Want to make sure we are not adding duplicates.
    profilesQueued: list of urls already added and being looped. Want to make sure we are not
        adding duplicates.
    visitedUsers: users already visited. Don't want to be creepy and visit them multiple days in a row.
    """

    return url not in profileURLS and url not in profilesQueued and "/in/" in url and "connections" not in url and "skills" not in url and url not in visitedUsers


def EndorseConnections(browser):
    """
    Endorse skills for your connections found. This only likes the top three popular
    skills the user has endorsed. If people want this feature can be further
    expanded just post an enhancement request in the repository.
    browser:
    """

    print("Gathering your connections url's to endorse their skills.")
    profileURLS = []
    browser.get('https://www.linkedin.com/mynetwork/invite-connect/connections/')
    time.sleep(3)

    try:
        for counter in range(1,NUM_LAZY_LOAD_ON_MY_NETWORK_PAGE):
            ScrollToBottomAndWaitForLoad(browser)

        soup = BeautifulSoup(browser.page_source, "lxml")
        for a in soup.find_all('a', class_='mn-person-info__picture'):
            if VERBOSE:
                print(a['href'])
            profileURLS.append(a['href'])

        print("Endorsing your connection's skills.")

        for url in profileURLS:

            endorseConnection = True
            if RANDOMIZE_ENDORSING_CONNECTIONS:
                endorseConnection = random.choice([True, False])

            if  endorseConnection:
                fullURL = 'https://www.linkedin.com'+url
                if VERBOSE:
                    print('Endorsing the connection '+fullURL)

                browser.get(fullURL)
                time.sleep(3)
                for button in browser.find_elements_by_xpath('//button[@data-control-name="endorse"]'):
                    button.click()
    except:
        print('Exception occurred when endorsing your connections.')
        pass

    print('') 

def getCompany(browser):
	'''
	Get first item in work experience on Linkedin
	browser = selenium webdriver
	Returns a single string that should be the job or nothing
	'''
	#A decent amount of false positives on this, more so than it makes me comfortable to actually push it. 
	#WIll probably revisit it but until then it wont be used.
	soup = BeautifulSoup(browser.page_source, PARSER)
	rtn = ""
	for tag in soup.findAll("span", {"class": "pv-entity__secondary-title"}):
		rtn = (tag.get_text())
		break
	
	if rtn != "":
		return rtn
	else:
		return("n/a")

def ScrollToBottomAndWaitForLoad(browser):
    """
    Scroll to the bottom of the page and wait for the page to perform it's lazy loading.
    browser: selenium webdriver used to interact with the browser.
    """

    browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(4)


if __name__ == '__main__':
    Launch()

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Author: <NAME>

import os, random, sys, time, datetime, csv, re
from configure import *
import urllib.parse as urlparse
from selenium import webdriver
from bs4 import BeautifulSoup
from random import shuffle


from os.path import join, dirname
# from dotenv import load_dotenv

# dotenv_path = join(dirname(__file__), '.env')
# load_dotenv(dotenv_path)


# Configurable Constants
EMAIL = '<EMAIL>'
PASSWORD ='<PASSWORD>'
VIEW_SPECIFIC_USERS = False
SPECIFIC_USERS_TO_VIEW = ['CEO', 'CTO', 'Developer', 'HR', 'Recruiter']
NUM_LAZY_LOAD_ON_MY_NETWORK_PAGE = 5
CONNECT_WITH_USERS = True
RANDOMIZE_CONNECTING_WITH_USERS = True
JOBS_TO_CONNECT_WITH = ['CEO', 'CTO', 'Developer', 'HR', 'Recruiter']
ENDORSE_CONNECTIONS = False
RANDOMIZE_ENDORSING_CONNECTIONS = True
VERBOSE = True
SESSION_CONNECTION_COUNT = 0
TEMP_NAME=""
TEMP_JOB=""
TEMP_JOBMATCH=""
TEMP_LOCATION=""
TEMP_LOCATIONMATCH=""
TEMP_PROFILE=[]
CONNECTED = False
TIME = str(time.time_ns())
CSV_DATA = [["Name","Title", "Title Match", "Location","Location Match", "Current Company","Connected"]]


def Launch():
    """
    Launch the LinkedIn bot.
    """

    # Check if the file 'visitedUsers.txt' exists, otherwise create it
    if os.path.isfile('visitedUsers.txt') == False:
        visitedUsersFile = open('visitedUsers.txt', 'wb')
        visitedUsersFile.close()

    # Browser choice
    print('Choose your browser:')
    print('[1] Chrome')
    print('[2] Firefox/Iceweasel')
    print('[3] PhantomJS')

    while True:
        try:
            browserChoice = int(1)
            # browserChoice = int(input('Choice? '))
        except ValueError:
            print('Invalid choice.'),
        else:
            if browserChoice not in [1,2,3]:
                print('Invalid choice.'),
            else:
                break

    StartBrowser(browserChoice)

def StartBrowser(browserChoice):
    """
    Launch broswer based on the user's selected choice.
    browserChoice: the browser selected by the user.
    """

    if browserChoice == 1:
        print('\nLaunching Chrome')
        browser = webdriver.Chrome()

    elif browserChoice == 2:
        print('\nLaunching Firefox/Iceweasel')
        browser = webdriver.Firefox()

    elif browserChoice == 3:
        print('\nLaunching PhantomJS')
        browser = webdriver.PhantomJS()

    # Sign in
    browser.get('https://linkedin.com/uas/login')
    emailElement = browser.find_element_by_id('username')
    emailElement.send_keys(EMAIL)
    passElement = browser.find_element_by_id('password')
    passElement.send_keys(PASSWORD)
    passElement.submit()

    print('Signing in...')
    time.sleep(3)

    soup = BeautifulSoup(browser.page_source, "lxml")
    if soup.find('div', {'class':'alert error'}):
        print('Error! Please verify your username and password.')
        browser.quit()
    elif browser.title == '403: Forbidden':
        print('LinkedIn is momentarily unavailable. Please wait a moment, then try again.')
        browser.quit()
    else:
        print('Success!\n')
        LinkedInBot(browser)


def LinkedInBot(browser):
    """
    Run the LinkedIn Bot.
    browser: the selenium driver to run the bot with.
    """

    T = 0
    V = 0
    profilesQueued = []
    error403Count = 0
    timer = time.time()

    if ENDORSE_CONNECTIONS:
        EndorseConnections(browser)

    if SCREENSHOTS:
        if PRINT_ACTIONS:
	        print("-> Enabled Screenshots")
        try:
            os.makedirs("Screenshots")
            if PRINT_ACTIONS:
                print("Created Screenshot Folder")
        except FileExistsError:
            pass
    header = ["Name","Title", "Title Match", "Location","Location Match", "Current Company", "Connected"]
    if SAVECSV:
        if PRINT_ACTIONS:
            print("-> Enabled Save as CSV")
        try:
            os.makedirs("CSV")
            if PRINT_ACTIONS:
                print("\t* Created CSV Folder")
        except FileExistsError:
            pass
        createCSV(header, TIME)
    if PRINT_ACTIONS:
        print('-> Scraping User URLs on Network tab.')

    # Infinite loop
    while True:

        # Generate random IDs
        while True:

            NavigateToMyNetworkPage(browser)
            T += 1

            if GetNewProfileURLS(BeautifulSoup(browser.page_source, "lxml"), profilesQueued):
                break
            else:
                print('|'),
                time.sleep(random.uniform(5, 7))

        soup = BeautifulSoup(browser.page_source, "lxml")
        profilesQueued = list(set(GetNewProfileURLS(soup, profilesQueued)))

        V += 1
        # print('\n\nGot our users to start viewing with!\n')
        # print(browser.title.replace(' | LinkedIn', ''), ' visited. T:', T, '| V:', V, '| Q:', len(profilesQueued))

        print('\t* Finished gathering User URLs.\n')
        print("--> Starting Process\n")

        while profilesQueued:

            shuffle(profilesQueued)
            profileID = profilesQueued.pop()
            browser.get('https://www.linkedin.com'+profileID)

            # Connect with users if the flag is turned on and matches your criteria
            if CONNECT_WITH_USERS:
                if not RANDOMIZE_CONNECTING_WITH_USERS:
                    ConnectWithUser(browser)
                elif random.choice([True, False]):
                    ConnectWithUser(browser)

            # Add the ID to the visitedUsersFile

            title_check = False
            title_check = TitleCheck(browser)
            location_check = LocationCheck(browser)

            if(CONNECT_BY_LOCATION):
                locationMatches = LocationCheck(browser)
                if VERBOSE:
                    print("Location Matches: "+str(locationMatches))
            else:
                locationMatches = False
            regex = r'\(.*?\)'
            TEMP_NAME = re.sub(regex, '', browser.title.replace(' | LinkedIn', ''))
            TEMP_JOB = JobMatch(browser)
            TEMP_LOCATION = LocationMatch(browser)
            #company = getCompany(browser)
            company ="n/a"
            title ="n/a"

            if " at " in TEMP_JOBMATCH:
                company = TEMP_JOBMATCH.split(" at ",1)[1]
            elif " for " in TEMP_JOBMATCH:
                company = TEMP_JOBMATCH.split(" for ",1)[1]
            if " at " in TEMP_JOBMATCH:
                title = TEMP_JOBMATCH.split(" at ",1)[0]
            elif " for " in TEMP_JOBMATCH:
                title = TEMP_JOBMATCH.split(" for ",1)[0]
                
            if POTENTIAL_COMPANY:
                if company == "n/a":
                    company = getCompany(browser)
            
            if VIEW_MODE.upper() == "BOX":
                tvq = str(T)+":"+str(V)+":"+str(len(profilesQueued))
                print("┌───────────────────────────────────┐")
                print("│● Name: %-26.26s │"%(TEMP_NAME))
                if CONNECT_BY_LOCATION and VIEW_SPECIFIC_TITLES:
                    print("├───────────────────────────────────┼───────────────────────────────────┬───────────────────────────────────┐")
                    print("│ Title Match: %-20.20s │ Location Match: %-17.17s │ T:V:Q %-28s│" %(TEMP_JOB, TEMP_LOCATION, tvq))
                elif CONNECT_BY_LOCATION:
                    print("│ Title Match: %-20.20s │ Location Match: %-17.17s │ T:V:Q %-28s│" %("OFF", TEMP_LOCATION, tvq))
                elif VIEW_SPECIFIC_TITLES:
                    print("│ Title Match: %-20.20s │ Location Match: %-17.17s │ T:V:Q %-28s│" %(TEMP_JOB, "OFF", tvq))
                else:
                    print("│ Title Match: %-20.20s │ Location Match: %-17.17s │ T:V:Q %-28s│" %("OFF", "OFF", tvq))
                if EXTRA_USER_INFO:
                    print("├───────────────────────────────────┼───────────────────────────────────┼───────────────────────────────────┤")
                    print("│ Title: %-26.26s │ Location: %-23.23s │ Company %-26.26s│" %(title,TEMP_LOCATIONMATCH, company))
                print("└───────────────────────────────────┴───────────────────────────────────┴───────────────────────────────────┘")
            elif VIEW_MODE.upper() == "COMPRESSED":
                if CONNECT_BY_LOCATION and VIEW_SPECIFIC_TITLES:
                    print("● Name: %-17.17s | Title Match: %-15.15s | Location Match: %-13.13s | T:V:Q %-3.3d:%-3.3d:%-3.3d" %(TEMP_NAME, TEMP_JOB, TEMP_LOCATION, T, V, len(profilesQueued)))
                elif CONNECT_BY_LOCATION:
                    print("● Name: %-17.17s | Location Match: %-13.13s | T:V:Q %-3.3d:%-3.3d:%-3.3d" %(TEMP_NAME, TEMP_LOCATION, T, V, len(profilesQueued)))
                elif VIEW_SPECIFIC_TITLES:
                    print("● Name: %-17.17s | Title Match: %-15.15s | T:V:Q %-3.3d:%-3.3d:%-3.3d" %(TEMP_NAME, TEMP_JOB, T, V, len(profilesQueued)))
                else:
                    print("● Name: %-17.17s | T:V:Q %-3.3d:%-3.3d:%-3.3d" %(TEMP_NAME, T, V, len(profilesQueued)))
                if EXTRA_USER_INFO:
                    print("↳       %17.17s | Title: %-21.21s | Location: %-19.19s | Company %-15.15s" %("",title,TEMP_LOCATIONMATCH, company))
            else:
                if CONNECT_BY_LOCATION and VIEW_SPECIFIC_TITLES:
                    print("● Name: %-17.17s | Title Match: %-15.15s | Location Match: %-13.13s | T:V:Q %-3.3d:%-3.3d:%-3.3d" %(TEMP_NAME, TEMP_JOB, TEMP_LOCATION, T, V, len(profilesQueued)))
                elif CONNECT_BY_LOCATION:
                    print("● Name: %-17.17s | Location Match: %-13.13s | T:V:Q %-3.3d:%-3.3d:%-3.3d" %(TEMP_NAME, TEMP_LOCATION, T, V, len(profilesQueued)))
                elif VIEW_SPECIFIC_TITLES:
                    print("● Name: %-17.17s | Title Match: %-15.15s | T:V:Q %-3.3d:%-3.3d:%-3.3d" %(TEMP_NAME, TEMP_JOB, T, V, len(profilesQueued)))
                else:
                    print("● Name: %-17.17s | T:V:Q %-3.3d:%-3.3d:%-3.3d" %(TEMP_NAME, T, V, len(profilesQueued)))
                if EXTRA_USER_INFO:
                    print("↳       %17.17s | Title: %-21.21s | Location: %-19.19s | Company %-15.15s" %("",title,TEMP_LOCATIONMATCH, company))
            
            if CONNECT_WITH_USERS and CONNECT_BY_LOCATION:
                if location_check:
                    if LIMIT_CONNECTION and (SESSION_CONNECTION_COUNT<CONNECTION_LIMIT):
                        if not RANDOMIZE_CONNECTING_WITH_USERS:
                            ConnectWithUser(browser)
                        elif random.choice([True, False]):
                            ConnectWithUser(browser)
                    elif not LIMIT_CONNECTION:
                        if not RANDOMIZE_CONNECTING_WITH_USERS:
                            ConnectWithUser(browser)
                        elif random.choice([True, False]):
                            ConnectWithUser(browser)
            elif CONNECT_WITH_USERS:
                if LIMIT_CONNECTION and (SESSION_CONNECTION_COUNT<CONNECTION_LIMIT):
                    if not RANDOMIZE_CONNECTING_WITH_USERS:
                        ConnectWithUser(browser)
                    elif random.choice([True, False]):
                        ConnectWithUser(browser)
                elif not LIMIT_CONNECTION:
                    if not RANDOMIZE_CONNECTING_WITH_USERS:
                        ConnectWithUser(browser)
                    elif random.choice([True, False]):
                        ConnectWithUser(browser)

            TEMP_PROFILE = [TEMP_NAME, TEMP_JOB, TEMP_JOBMATCH, TEMP_LOCATION, TEMP_LOCATIONMATCH, company, CONNECTED]
            if SAVECSV:
                addToCSV(TEMP_PROFILE, TIME)
                if VERBOSE:
                    print("-> Temp Profile List")
                    print(TEMP_PROFILE)

                        
            with open('visitedUsers.txt', 'a') as visitedUsersFile:
                visitedUsersFile.write(profileID+'\r\n')
            visitedUsersFile.close()

            # Get new profiles ID
            time.sleep(10)
            soup = BeautifulSoup(browser.page_source, PARSER)
            profilesQueued.extend(GetNewProfileURLS(soup, profilesQueued))
            profilesQueued = list(set(profilesQueued))

            browserTitle = (browser.title).replace('  ',' ')

            

            # 403 error
            if browserTitle == '403: Forbidden':
                error403Count += 1
                print('\nLinkedIn is momentarily unavailable - Paused for', str(error403Count), 'hour(s)\n')
                time.sleep(3600*error403Count+(random.randrange(0, 10))*60)
                timer = time.time() # Reset the timer

            # User out of network
            elif browserTitle == 'Profile | LinkedIn':
                T += 1
                error403Count = 0
                print('User not in your network. T:', T, '| V:', V, '| Q:', len(profilesQueued))

            # User in network
            else:
                T += 1
                V += 1
                error403Count = 0
                print(browserTitle.replace(' | LinkedIn', ''), 'visited. T:', T, '| V:', V, '| Q:', len(profilesQueued))

            # Pause
            if (T%1000==0) or time.time()-timer > 3600:
                print('\nPaused for 1 hour\n')
                time.sleep(3600+(random.randrange(0, 10))*60)
                timer = time.time() # Reset the timer
            else:
                time.sleep(random.uniform(5, 7)) # Otherwise, sleep to make sure everything loads

        print('\nNo more profiles to visit. Everything restarts with the network page...\n')


def LocationCheck(browser):
	'''
	Checks if the location of the user matches your list of locations
	browser = selenium webdriver
	returns true or false
	'''
	soup = BeautifulSoup(browser.page_source, PARSER)
	
	ul = soup.find("ul", {"class": "pv-top-card--list"})
	ul = ul.find_next("ul", {"class": "pv-top-card--list"})
	li = ul.find_next("li")
	for l in LOCATIONS_TO_CONNECT:
		if VERBOSE:
			print(">>>> Web Location: "+str(li.text)+" Settings Location: "+str(l))
		if l.lower() in li.text.lower():
			return True
		else:
			return False

def TitleCheck(browser):

    soup = BeautifulSoup(browser.page_source, PARSER)
    ul = soup.find("ul", {"class": "pv-top-card--list"})
    h2 = ul.find_next("h2")

    
    for job in TITLES_TO_VIEW_CONNECT_WITH:
        if VERBOSE:
            print(">>>> Web Job/Title: "+str(h2.text)+" Settings Job/Title: "+str(job))
        if job.lower() in h2.text.lower():
            return True
        else:
            return False

def LocationMatch(browser):
	'''
	Gets the location that matches your settings and also sets TEMP_LOCATIONMATCH
	browser = selenium webdriver
	returns the locaiton as a string
	'''
	global TEMP_LOCATIONMATCH
	TEMP_LOCATIONMATCH = "X" #if this doesnt change it, it could have the previous connections data
	soup = BeautifulSoup(browser.page_source, PARSER)
	rtn = ""
	ul = soup.find("ul", {"class": "pv-top-card--list"})
	ul = ul.find_next("ul", {"class": "pv-top-card--list"})
	li = ul.find_next("li")
	for l in LOCATIONS_TO_CONNECT:
		if l.lower() in li.text.lower():
			TEMP_LOCATIONMATCH = str(" ".join((li.text.lower()).split()))
			location = str(l)
			rtn = location
			if VERBOSE:
				print(">>>> Location Match: "+rtn)
				print(">>>>"+location + " : " + TEMP_LOCATIONMATCH)
	if rtn != "":
		return rtn
	else:
		return("X")

def JobMatch(browser):
	'''
	Gets the job that matches your settings and also sets TEMP_JOBMATCH
	browser = selenium webdriver
	returns the job as a string
	'''

	global TEMP_JOBMATCH
	TEMP_JOBMATCH = "X" #if this doesnt change it, it could have the previous connections data
	soup = BeautifulSoup(browser.page_source, PARSER)
	rtn = ""
	ul = soup.find("ul", {"class": "pv-top-card--list"})
	li = ul.find_next("h2")
	for job in TITLES_TO_VIEW_CONNECT_WITH:
		if job.lower() in li.text.lower():
			TEMP_JOBMATCH = (" ".join((li.text.lower()).split()))
			jobtitle = str(job)
			rtn = jobtitle
			if VERBOSE:
				print(">>>> Job Match: "+rtn)
				print(">>>>"+jobtitle + " : " + TEMP_JOBMATCH)
	if rtn != "":
		return rtn
	else:
		return("X")

def createCSV(data, time):
	'''
	Creates initial CSV file
	data is the list that will get added to the file(in this case its the headers)
	time is the time at creation of this file
	'''
	filename = 'Linked-In-'+ time +'.csv'
	with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'CSV', filename), 'w',newline='') as csvFile:
		writer = csv.writer(csvFile)
		writer.writerow(data)
	csvFile.close()

def addToCSV(data, time):

    filename = 'Linked-In-'+ time +'.csv'
    if 2 < len(data):
        with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'CSV', filename), 'a',encoding='utf8', newline='') as csvFile:
            writer = csv.writer(csvFile, dialect='excel')
            writer.writerow(data)
        csvFile.close()

def NavigateToMyNetworkPage(browser):
    """
    Navigate to the my network page and scroll to the bottom and let the lazy loading
    go to be able to grab more potential users in your network. It is reccommended to
    increase the NUM_LAZY_LOAD_ON_MY_NETWORK_PAGE value if you are using the variable
    SPECIFIC_USERS_TO_VIEW.
    browser: the selenium browser used to interact with the page.
    """

    browser.get('https://www.linkedin.com/mynetwork/')
    for counter in range(1,NUM_LAZY_LOAD_ON_MY_NETWORK_PAGE):
        ScrollToBottomAndWaitForLoad(browser)


def ConnectWithUser(browser):
    """
    Connect with the user viewing if their job title is found in your list of roles
    you want to connect with.
    browse: the selenium browser used to interact with the page.
    """

    soup = BeautifulSoup(browser.page_source, "lxml")
    jobTitleMatches = False

    # I know not that efficient of a loop but BeautifulSoup and Selenium are
    # giving me a hard time finding the specifc h2 element that contain's user's job title
    for h2 in soup.find_all('h2'):
        for job in JOBS_TO_CONNECT_WITH:
            if job in h2.getText():
                jobTitleMatches = True
                break

    if jobTitleMatches:
        try:
            if VERBOSE:
                print('Sending the user an invitation to connect.')
                browser.find_element_by_xpath('//button[@class="pv-s-profile-actions pv-s-profile-actions--connect ml2 artdeco-button artdeco-button--2 artdeco-button--primary ember-view"]').click() #old class = connect primary top-card-action ember-view
                time.sleep(random.randrange(3))
                browser.find_element_by_xpath('//button[@class="ml1 artdeco-button artdeco-button--3 artdeco-button--primary ember-view"]').click()
        except:
            pass


def GetNewProfileURLS(soup, profilesQueued):
    """
    Get new profile urls to add to the navigate queue.
    soup: beautiful soup instance of page's source code.
    profileQueued: current list of profile queues.
    """

    # Open, load and close
    with open('visitedUsers.txt', 'r') as visitedUsersFile:
        visitedUsers = [line.strip() for line in visitedUsersFile]
    visitedUsersFile.close()

    profileURLS = []
    profileURLS.extend(FindProfileURLsInNetworkPage(soup, profilesQueued, profileURLS, visitedUsers))
    profileURLS.extend(FindProfileURLsInPeopleAlsoViewed(soup, profilesQueued, profileURLS, visitedUsers))
    profileURLS.extend(FindProfileURLsInEither(soup, profilesQueued, profileURLS, visitedUsers))
    profileURLS = list(set(profileURLS))
    return profileURLS


def FindProfileURLsInNetworkPage(soup, profilesQueued, profileURLS, visitedUsers):
    """
    Get new profile urls to add to the navigate queue from the my network page.
    soup: beautiful soup instance of page's source code.
    profileQueued: current list of profile queues.
    profileURLS: profile urls already found this scrape.
    visitedUsers: user's profiles that we have already viewed.
    """

    newProfileURLS = []

    try:
        for a in soup.find_all('a', class_='discover-entity-type-card__link'):
            if ValidateURL(a['href'], profileURLS, profilesQueued, visitedUsers):

                if VIEW_SPECIFIC_USERS:
                    for span in a.find_all('span', class_='discover-person-card__occupation'):
                        for occupation in SPECIFIC_USERS_TO_VIEW:
                            if occupation.lower() in span.text.lower():
                                if VERBOSE:
                                    print(a['href'])
                                newProfileURLS.append(a['href'])
                                break

                else:
                    if VERBOSE:
                        print(a['href'])
                    newProfileURLS.append(a['href'])
    except:
        pass

    return newProfileURLS


def FindProfileURLsInPeopleAlsoViewed(soup, profilesQueued, profileURLS, visitedUsers):
    """
    Get new profile urls to add to the navigate queue from the people also viewed section.
    soup: beautiful soup instance of page's source code.
    profileQueued: current list of profile queues.
    profileURLS: profile urls already found this scrape.
    visitedUsers: user's profiles that we have already viewed.
    """

    newProfileURLS = []

    try:
        for a in soup.find_all('a', class_='pv-browsemap-section__member'):
            if ValidateURL(a['href'], profileURLS, profilesQueued, visitedUsers):

                if VIEW_SPECIFIC_USERS:
                    for div in a.find_all('div'):
                        for occupation in SPECIFIC_USERS_TO_VIEW:
                            if occupation.lower() in div.text.lower():
                                if VERBOSE:
                                    print(a['href'])
                                newProfileURLS.append(a['href'])
                                break

                else:
                    if VERBOSE:
                        print(a['href'])
                    newProfileURLS.append(a['href'])
    except:
        pass

    return newProfileURLS


def FindProfileURLsInEither(soup, profilesQueued, profileURLS, visitedUsers):
    """
    Get new profile urls to add to the navigate queue, some use different class
    names in the my network page and people also viewed section.
    soup: beautiful soup instance of page's source code.
    profileQueued: current list of profile queues.
    profileURLS: profile urls already found this scrape.
    visitedUsers: user's profiles that we have already viewed.
    """

    newProfileURLS = []

    try:
        for ul in soup.find_all('ul', class_='pv-profile-section__section-info'):
            for li in ul.find_all('li'):
                a = li.find('a')
                if ValidateURL(a['href'], profileURLS, profilesQueued, visitedUsers):

                    if VIEW_SPECIFIC_USERS:
                        for div in a.find_all('div'):
                            for occupatio in SPECIFIC_USERS_TO_VIEW:
                                if VERBOSE:
                                    print(">>>" + occupatio)
                                if occupatio.lower() in div.text.lower():
                                    if VERBOSE:
                                        print(a['href'])
                                    profileURLS.append(a['href'])
                                    break

                    else:
                        if VERBOSE:
                            print(a['href'])
                        profileURLS.append(a['href'])
    except:
        pass

    return newProfileURLS


def ValidateURL(url, profileURLS, profilesQueued, visitedUsers):
    """
    Validate the url passed meets requirement to be navigated to.
    profileURLS: list of urls already added within the GetNewProfileURLS method to be returned.
        Want to make sure we are not adding duplicates.
    profilesQueued: list of urls already added and being looped. Want to make sure we are not
        adding duplicates.
    visitedUsers: users already visited. Don't want to be creepy and visit them multiple days in a row.
    """

    return url not in profileURLS and url not in profilesQueued and "/in/" in url and "connections" not in url and "skills" not in url and url not in visitedUsers


def EndorseConnections(browser):
    """
    Endorse skills for your connections found. This only likes the top three popular
    skills the user has endorsed. If people want this feature can be further
    expanded just post an enhancement request in the repository.
    browser:
    """

    print("Gathering your connections url's to endorse their skills.")
    profileURLS = []
    browser.get('https://www.linkedin.com/mynetwork/invite-connect/connections/')
    time.sleep(3)

    try:
        for counter in range(1,NUM_LAZY_LOAD_ON_MY_NETWORK_PAGE):
            ScrollToBottomAndWaitForLoad(browser)

        soup = BeautifulSoup(browser.page_source, "lxml")
        for a in soup.find_all('a', class_='mn-person-info__picture'):
            if VERBOSE:
                print(a['href'])
            profileURLS.append(a['href'])

        print("Endorsing your connection's skills.")

        for url in profileURLS:

            endorseConnection = True
            if RANDOMIZE_ENDORSING_CONNECTIONS:
                endorseConnection = random.choice([True, False])

            if  endorseConnection:
                fullURL = 'https://www.linkedin.com'+url
                if VERBOSE:
                    print('Endorsing the connection '+fullURL)

                browser.get(fullURL)
                time.sleep(3)
                for button in browser.find_elements_by_xpath('//button[@data-control-name="endorse"]'):
                    button.click()
    except:
        print('Exception occurred when endorsing your connections.')
        pass

    print('') 

def getCompany(browser):
	'''
	Get first item in work experience on Linkedin
	browser = selenium webdriver
	Returns a single string that should be the job or nothing
	'''
	#A decent amount of false positives on this, more so than it makes me comfortable to actually push it. 
	#WIll probably revisit it but until then it wont be used.
	soup = BeautifulSoup(browser.page_source, PARSER)
	rtn = ""
	for tag in soup.findAll("span", {"class": "pv-entity__secondary-title"}):
		rtn = (tag.get_text())
		break
	
	if rtn != "":
		return rtn
	else:
		return("n/a")

def ScrollToBottomAndWaitForLoad(browser):
    """
    Scroll to the bottom of the page and wait for the page to perform it's lazy loading.
    browser: selenium webdriver used to interact with the browser.
    """

    browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(4)


if __name__ == '__main__':
    Launch()
#!/usr/bin/python # -*- coding: utf-8 -*- # Author: <NAME> # from dotenv import load_dotenv # dotenv_path = join(dirname(__file__), '.env') # load_dotenv(dotenv_path) # Configurable Constants Launch the LinkedIn bot. # Check if the file 'visitedUsers.txt' exists, otherwise create it # Browser choice # browserChoice = int(input('Choice? ')) Launch broswer based on the user's selected choice.     browserChoice: the browser selected by the user. # Sign in Run the LinkedIn Bot.     browser: the selenium driver to run the bot with. # Infinite loop # Generate random IDs # print('\n\nGot our users to start viewing with!\n') # print(browser.title.replace(' | LinkedIn', ''), ' visited. T:', T, '| V:', V, '| Q:', len(profilesQueued)) # Connect with users if the flag is turned on and matches your criteria # Add the ID to the visitedUsersFile #company = getCompany(browser) # Get new profiles ID # 403 error # Reset the timer # User out of network # User in network # Pause # Reset the timer # Otherwise, sleep to make sure everything loads Checks if the location of the user matches your list of locations 	browser = selenium webdriver 	returns true or false Gets the location that matches your settings and also sets TEMP_LOCATIONMATCH 	browser = selenium webdriver 	returns the locaiton as a string #if this doesnt change it, it could have the previous connections data Gets the job that matches your settings and also sets TEMP_JOBMATCH 	browser = selenium webdriver 	returns the job as a string #if this doesnt change it, it could have the previous connections data Creates initial CSV file 	data is the list that will get added to the file(in this case its the headers) 	time is the time at creation of this file Navigate to the my network page and scroll to the bottom and let the lazy loading     go to be able to grab more potential users in your network. It is reccommended to     increase the NUM_LAZY_LOAD_ON_MY_NETWORK_PAGE value if you are using the variable     SPECIFIC_USERS_TO_VIEW.     browser: the selenium browser used to interact with the page. Connect with the user viewing if their job title is found in your list of roles     you want to connect with.     browse: the selenium browser used to interact with the page. # I know not that efficient of a loop but BeautifulSoup and Selenium are # giving me a hard time finding the specifc h2 element that contain's user's job title #old class = connect primary top-card-action ember-view Get new profile urls to add to the navigate queue.     soup: beautiful soup instance of page's source code.     profileQueued: current list of profile queues. # Open, load and close Get new profile urls to add to the navigate queue from the my network page.     soup: beautiful soup instance of page's source code.     profileQueued: current list of profile queues.     profileURLS: profile urls already found this scrape.     visitedUsers: user's profiles that we have already viewed. Get new profile urls to add to the navigate queue from the people also viewed section.     soup: beautiful soup instance of page's source code.     profileQueued: current list of profile queues.     profileURLS: profile urls already found this scrape.     visitedUsers: user's profiles that we have already viewed. Get new profile urls to add to the navigate queue, some use different class     names in the my network page and people also viewed section.     soup: beautiful soup instance of page's source code.     profileQueued: current list of profile queues.     profileURLS: profile urls already found this scrape.     visitedUsers: user's profiles that we have already viewed. Validate the url passed meets requirement to be navigated to.     profileURLS: list of urls already added within the GetNewProfileURLS method to be returned.         Want to make sure we are not adding duplicates.     profilesQueued: list of urls already added and being looped. Want to make sure we are not         adding duplicates.     visitedUsers: users already visited. Don't want to be creepy and visit them multiple days in a row. Endorse skills for your connections found. This only likes the top three popular     skills the user has endorsed. If people want this feature can be further     expanded just post an enhancement request in the repository.     browser: Get first item in work experience on Linkedin 	browser = selenium webdriver 	Returns a single string that should be the job or nothing #A decent amount of false positives on this, more so than it makes me comfortable to actually push it. #WIll probably revisit it but until then it wont be used. Scroll to the bottom of the page and wait for the page to perform it's lazy loading.     browser: selenium webdriver used to interact with the browser.