diff --git a/.idea/artifacts/JobApplications_jar.xml b/.idea/artifacts/JobApplications_jar.xml new file mode 100644 index 0000000..aea4083 --- /dev/null +++ b/.idea/artifacts/JobApplications_jar.xml @@ -0,0 +1,58 @@ + + + $PROJECT_DIR$/out/artifacts/JobApplications_jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0dba17b..82234e1 100644 --- a/pom.xml +++ b/pom.xml @@ -75,6 +75,12 @@ kotlinx-coroutines-core 1.3.3 + + org.jetbrains.kotlin + kotlin-reflect + 1.3.61 + compile + @@ -119,6 +125,8 @@ + true + lib/ Main diff --git a/src/main/java/JobObject.kt b/src/main/java/JobObject.kt index 43eeaf5..5617bc0 100644 --- a/src/main/java/JobObject.kt +++ b/src/main/java/JobObject.kt @@ -1,3 +1,5 @@ +import api.network.responses.ReedJobObject +import java.time.LocalDateTime import java.util.* data class JobObject( @@ -10,7 +12,12 @@ data class JobObject( var dateApplied : String? = null ){ - override fun toString(): String { - return super.toString() - } + constructor(job: ReedJobObject): this( + jobId = job.jobId.toString(), + website = job.jobUrl, + jobTitle = job.jobTitle, + location = job.locationName, + company = job.employerName, + dateApplied = LocalDateTime.now().toString() + ) } \ No newline at end of file diff --git a/src/main/java/LegacyCode.kt b/src/main/java/LegacyCode.kt index f6d1be4..92a88d6 100644 --- a/src/main/java/LegacyCode.kt +++ b/src/main/java/LegacyCode.kt @@ -11,111 +11,111 @@ import kotlin.math.roundToInt fun StartSearch(){ - //Open Chrome - System.setProperty("webdriver.chrome.driver","C:\\Selenium\\selenium-java-3.141.59\\chromedriver_win32\\chromedriver.exe" ) - val driver = ChromeDriver() - - //open reed website login - driver.get("https://www.reed.co.uk/account/signin?returnUrl=%2F#&card=signin") - val wait = WebDriverWait(driver, 20) - - //wait for page to load - val lastElementToLoad = driver.findElementById("signin-button") - wait.until(ExpectedConditions.elementToBeClickable(lastElementToLoad)) - - //insert credentials and sign in - driver.findElementByXPath("//*[@id=\"Credentials_Email\"]").sendKeys(REED_USERNAME) - driver.findElementByXPath("//*[@id=\"Credentials_Password\"]").sendKeys(REED_PASSWORD) - lastElementToLoad.click() - - //wait for page to load - val jobSearchEditText = driver.findElementByXPath("//*[@id=\"keywords\"]") - wait.until(ExpectedConditions.elementToBeClickable(jobSearchEditText)) - -// //submit search - jobSearchEditText.sendKeys(REED_KEYWORDS) - driver.findElementByXPath("//*[@id=\"location\"]").sendKeys(REED_LOCATION) - driver.findElementByXPath("//*[@id=\"main-search\"]/div[1]/div[3]/button").click() - - //todo: change to wait - Thread.sleep(1500) - - val ad = driver.findElementByXPath("//*[@id=\"content\"]/div[1]/div[2]/h1") - wait.until(ExpectedConditions.elementToBeClickable(ad)) - - //find number of pages - val text = driver.findElementByCssSelector("div.page-counter").text /* eg. 1 - 25 of 99 jobs */ - print(text) - val count = text.toTotalCount() - val pages = count.getNumberOfPages() - - //loop through pages of search - for (i in 1..pages){ - - //open page by number on search - //todo: change this url builder - driver.get("https://www.reed.co.uk/jobs/android-developer-jobs-in-kilburn-london?pageno=$i") - Thread.sleep(2500) - - //elements list of jobs on page - val list = driver.findElementsByCssSelector("div.col-sm-12.col-md-9.col-lg-10.details") - - //turn list into global list job object - list.forEach { - val badge = it.findElement(By.cssSelector("div.badge-container")) - //check if there is a badge element - if (badge.isDisplayed){ - //see if applied is in badge - val applied = badge.findElements(By.cssSelector("span.label.label-applied")) - //if applied doesnt exist then add to global list of jobs - if (applied.isNullOrEmpty()){ - val jobObject = it.toJobObject() - jobsList.add(jobObject) - } - - }else{ - //no badge exists so add to list of jobs declared at the top - val jobObject = it.toJobObject() - jobsList.add(jobObject) - } - } - } - - //loop through the jobs collected - jobsList.forEach{ - //open the URl - driver.get(it.url) - - val title = driver.findElementByXPath("//*[@id=\"content\"]/div/div[2]/article") - wait.until(ExpectedConditions.elementToBeClickable(title)) - - //check for external apply element - val applyExternal = driver.findElementsByCssSelector("span.external-app-caption") - - //if external apply is empty then apply for job - if (applyExternal.isNullOrEmpty()){ - - print(it.jobTitle + " ${it.url} \n" ) - - - //find apply button - val applyNow = driver.findElementsByXPath("//*[@id=\"applyButtonSide\"]") - - if (!applyNow.isNullOrEmpty()){ - - //click apply - applyNow[1].click() - - try{ - val successfulApplied = driver.findElementByXPath("//*[@id=\"content\"]/div/div[1]/a") - wait.until(ExpectedConditions.visibilityOf(successfulApplied)) - }catch (e: Exception){ - println(it.jobId + " did not apply") - println("\n" + e.toString() + "\n") - } - } - } - } +// //Open Chrome +// System.setProperty("webdriver.chrome.driver","C:\\Selenium\\selenium-java-3.141.59\\chromedriver_win32\\chromedriver.exe" ) +// val driver = ChromeDriver() +// +// //open reed website login +// driver.get("https://www.reed.co.uk/account/signin?returnUrl=%2F#&card=signin") +// val wait = WebDriverWait(driver, 20) +// +// //wait for page to load +// val lastElementToLoad = driver.findElementById("signin-button") +// wait.until(ExpectedConditions.elementToBeClickable(lastElementToLoad)) +// +// //insert credentials and sign in +// driver.findElementByXPath("//*[@id=\"Credentials_Email\"]").sendKeys(REED_USERNAME) +// driver.findElementByXPath("//*[@id=\"Credentials_Password\"]").sendKeys(REED_PASSWORD) +// lastElementToLoad.click() +// +// //wait for page to load +// val jobSearchEditText = driver.findElementByXPath("//*[@id=\"keywords\"]") +// wait.until(ExpectedConditions.elementToBeClickable(jobSearchEditText)) +// +//// //submit search +// jobSearchEditText.sendKeys(REED_KEYWORDS) +// driver.findElementByXPath("//*[@id=\"location\"]").sendKeys(REED_LOCATION) +// driver.findElementByXPath("//*[@id=\"main-search\"]/div[1]/div[3]/button").click() +// +// //todo: change to wait +// Thread.sleep(1500) +// +// val ad = driver.findElementByXPath("//*[@id=\"content\"]/div[1]/div[2]/h1") +// wait.until(ExpectedConditions.elementToBeClickable(ad)) +// +// //find number of pages +// val text = driver.findElementByCssSelector("div.page-counter").text /* eg. 1 - 25 of 99 jobs */ +// print(text) +// val count = text.toTotalCount() +// val pages = count.getNumberOfPages() +// +// //loop through pages of search +// for (i in 1..pages){ +// +// //open page by number on search +// //todo: change this url builder +// driver.get("https://www.reed.co.uk/jobs/android-developer-jobs-in-kilburn-london?pageno=$i") +// Thread.sleep(2500) +// +// //elements list of jobs on page +// val list = driver.findElementsByCssSelector("div.col-sm-12.col-md-9.col-lg-10.details") +// +// //turn list into global list job object +// list.forEach { +// val badge = it.findElement(By.cssSelector("div.badge-container")) +// //check if there is a badge element +// if (badge.isDisplayed){ +// //see if applied is in badge +// val applied = badge.findElements(By.cssSelector("span.label.label-applied")) +// //if applied doesnt exist then add to global list of jobs +// if (applied.isNullOrEmpty()){ +// val jobObject = it.toJobObject() +// jobsList.add(jobObject) +// } +// +// }else{ +// //no badge exists so add to list of jobs declared at the top +// val jobObject = it.toJobObject() +// jobsList.add(jobObject) +// } +// } +// } +// +// //loop through the jobs collected +// jobsList.forEach{ +// //open the URl +// driver.get(it.url) +// +// val title = driver.findElementByXPath("//*[@id=\"content\"]/div/div[2]/article") +// wait.until(ExpectedConditions.elementToBeClickable(title)) +// +// //check for external apply element +// val applyExternal = driver.findElementsByCssSelector("span.external-app-caption") +// +// //if external apply is empty then apply for job +// if (applyExternal.isNullOrEmpty()){ +// +// print(it.jobTitle + " ${it.url} \n" ) +// +// +// //find apply button +// val applyNow = driver.findElementsByXPath("//*[@id=\"applyButtonSide\"]") +// +// if (!applyNow.isNullOrEmpty()){ +// +// //click apply +// applyNow[1].click() +// +// try{ +// val successfulApplied = driver.findElementByXPath("//*[@id=\"content\"]/div/div[1]/a") +// wait.until(ExpectedConditions.visibilityOf(successfulApplied)) +// }catch (e: Exception){ +// println(it.jobId + " did not apply") +// println("\n" + e.toString() + "\n") +// } +// } +// } +// } } diff --git a/src/main/java/ListToExcel.kt b/src/main/java/ListToExcel.kt new file mode 100644 index 0000000..d919902 --- /dev/null +++ b/src/main/java/ListToExcel.kt @@ -0,0 +1,80 @@ +import api.network.responses.ReedJobObject +import org.apache.poi.xssf.usermodel.XSSFWorkbook +import java.awt.Desktop +import java.io.File +import java.io.FileOutputStream +import java.time.LocalDate +import kotlin.reflect.full.memberProperties + + +fun List.writeToOut(){ + try { + val workbook = XSSFWorkbook() + val sheet = workbook.createSheet("sheet1") // creating a blank sheet + + val headerList = takeKClass() + val topRow = sheet.createRow(0) + ReedJobObject::class.memberProperties.forEachIndexed { headerPos, item -> + topRow.createCell(headerPos).setCellValue(headerList[headerPos]) + } + + this.forEachIndexed { index, reedJobObject -> + val row = sheet.createRow(index + 1) + + ReedJobObject::class.memberProperties.forEachIndexed { headerPos, item -> + row.createCell(headerPos).setCellValue(item.get(reedJobObject).toString()) + } + } + + val dateTime = LocalDate.now().toString() + val file = File("E:\\Reed search output\\Jobs applied - ${dateTime}.xlsx") + val out = FileOutputStream(file) // file name with path + workbook.write(out) + out.close() + val desktop = Desktop.getDesktop() + if (file.exists()) //checks file exists or not + desktop.open(file) //opens the specified file + } catch (e: Exception) { + e.printStackTrace() + } +} + +//inline fun List.writeToExcel() { +// try { +// val workbook = XSSFWorkbook() +// val sheet = workbook.createSheet("sheet1") // creating a blank sheet +// var rownum = 0 +// +// val headerList = takeKClass() +// +// +// for (user in this) { +// +// val row: Row = sheet.createRow(rownum++) +// createList(user, row) +// } +// val out = FileOutputStream(File("NewFile.xlsx")) // file name with path +// workbook.write(out) +// out.close() +// } catch (e: Exception) { +// e.printStackTrace() +// } +//} + + +inline fun takeKClass(): List { + val reflection = T::class + + return reflection.members.map { it.name } +} + +inline fun List.getElements(): List> { + val reflection = T::class + + return this.map { + reflection.memberProperties.map { kProperty1 -> + kProperty1.get(it).toString() + } + } + +} diff --git a/src/main/java/Main.kt b/src/main/java/Main.kt index cf8aa40..c049d94 100644 --- a/src/main/java/Main.kt +++ b/src/main/java/Main.kt @@ -4,72 +4,78 @@ import api.network.NetworkRequests import api.network.responses.ReedJobObject import org.openqa.selenium.JavascriptExecutor import org.openqa.selenium.chrome.ChromeDriver -import org.openqa.selenium.support.ui.ExpectedConditions import org.openqa.selenium.support.ui.WebDriverWait +import kotlin.system.exitProcess -var jobsList = mutableListOf() +var appliedJobsList = mutableListOf() var reedJobsList = listOf() lateinit var driver: ChromeDriver lateinit var wait: WebDriverWait +const val driverPath = "C:\\Selenium\\selenium-java-3.141.59\\chromedriver_win32\\chromedriver.exe" public fun main(args: Array){ - setup() + getJobsFromReedApi() setupWebDriver() logonToReed() - applyForJobsThroughLoop() + applyForJobsThroughLoop { + appliedJobsList.writeToOut() + driver.close() + exitProcess(2) + } } -fun setup(){ +fun getJobsFromReedApi(){ val result = NetworkRequests().getSearchApi() - - if (!result.isNullOrEmpty()){ - reedJobsList = result + result?.let { + reedJobsList = it + return } - + // No results found so exit + exitProcess(2) } fun setupWebDriver(){ //Open Chrome - System.setProperty("webdriver.chrome.driver","C:\\Selenium\\selenium-java-3.141.59\\chromedriver_win32\\chromedriver.exe" ) + System.setProperty("webdriver.chrome.driver", driverPath) driver = ChromeDriver() wait = WebDriverWait(driver, 20) } -fun applyForJobsThroughLoop(){ - - reedJobsList.forEach { - try { - driver.get(it.jobUrl) - applyForJob(it) - - }catch (e: Exception){ - println("\n" + e.toString() + "\n") - } - } -} - fun logonToReed(){ driver.get("https://www.reed.co.uk/account/signin?returnUrl=%2F#&card=signin") - - //wait for page to load - val lastElementToLoad = driver.findElementById("signin-button") - wait.until(ExpectedConditions.elementToBeClickable(lastElementToLoad)) + waitForPageToLoad() //insert credentials and sign in driver.findElementByXPath("//*[@id=\"Credentials_Email\"]").sendKeys(REED_USERNAME) driver.findElementByXPath("//*[@id=\"Credentials_Password\"]").sendKeys(REED_PASSWORD) - lastElementToLoad.click() - //wait for page to load - val jobSearchEditText = driver.findElementByXPath("//*[@id=\"keywords\"]") - wait.until(ExpectedConditions.elementToBeClickable(jobSearchEditText)) + // Click login and wait for page to load + driver.findElementById("signin-button").click() + waitForPageToLoad() +} + +fun applyForJobsThroughLoop( + complete: () -> Unit +){ + reedJobsList.forEach { + try { + // load url + driver.get(it.jobUrl) + // load [age + waitForPageToLoad() + // apply for job + applyForJob(it) + }catch (e: Exception){ + e.printStackTrace() + } + } + complete() } fun applyForJob(jobObject: ReedJobObject){ - val appliedBefore = driver.findElementsByXPath("//*[@id=\"content\"]/div/div[2]/article/div/div[1]/div") - if (appliedBefore.isNullOrEmpty()){ + if (hasUserNotAppliedBefore()){ println("${jobObject.jobId} has not been applied") //find apply button val applyNow = driver.findElementsByXPath("//*[@id=\"applyButtonSide\"]") @@ -78,22 +84,27 @@ fun applyForJob(jobObject: ReedJobObject){ //click apply val index = if (applyNow.size > 1){ 1 }else{ 0 } applyNow[index].click() - - try{ -// val successfulApplied = driver.findElementByCssSelector("div.alert.alert-success alert-borderless") - wait.until{ - driver.executeScript("return document.readyState") == "complete" - } - - }catch (e: Exception){ - println("\n" + e.toString() + "\n") - } + waitForPageToLoad() + println("${jobObject.jobId} has now been applied for") + appliedJobsList.add(jobObject) + return } + println("${jobObject.jobId} could not be applied for") }else{ - println("${jobObject.jobId} has been applied") + println("${jobObject.jobId} has been applied for previously") } - - - } +// Checks to see if user has applied before +fun hasUserNotAppliedBefore(): Boolean{ + // find "applied before" text + val appliedBefore = driver.findElementsByXPath("//*[@id=\"content\"]/div/div[2]/article/div/div[1]/div") + return appliedBefore.isNullOrEmpty() +} + +fun waitForPageToLoad(){ + //wait for page to load + wait.until{driv -> + (driv as JavascriptExecutor).executeScript("return document.readyState") == "complete" + } +} diff --git a/src/main/java/api/network/NetworkRequests.kt b/src/main/java/api/network/NetworkRequests.kt index ce3c3ae..5faa69b 100644 --- a/src/main/java/api/network/NetworkRequests.kt +++ b/src/main/java/api/network/NetworkRequests.kt @@ -1,30 +1,29 @@ package api.network import Constants.Companion.REED_KEYWORDS -import Constants.Companion.REED_LOCATION import Constants.Companion.REED_MINIMUM_SALARY import api.network.responses.ReedJobObject import com.example.h_mal.androiddevelopertechtest_incrowdsports.data.network.ReedApi import com.example.h_mal.androiddevelopertechtest_incrowdsports.data.network.SafeApiRequest -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking class NetworkRequests : SafeApiRequest(){ - val reedApi = ReedApi() + val reedApi by lazy { ReedApi() } fun getSearchApi() : List?{ try { - val response = runBlocking { apiRequest { reedApi.getGameData(REED_KEYWORDS, REED_LOCATION, REED_MINIMUM_SALARY) } } + // get results from Api + val response = runBlocking { + apiRequest { reedApi.getReedData(REED_KEYWORDS, REED_MINIMUM_SALARY) } + } + // check we have results response.results?.let { return it } - }catch (e : Exception){ - println("*** $e") + e.printStackTrace() } return null } diff --git a/src/main/java/api/network/ReedApi.kt b/src/main/java/api/network/ReedApi.kt index 763dd31..28bfc3e 100644 --- a/src/main/java/api/network/ReedApi.kt +++ b/src/main/java/api/network/ReedApi.kt @@ -14,12 +14,22 @@ import retrofit2.http.Query interface ReedApi { - //get the game data from api call of relevent gameID + //get the job data from api call to Reed api data service @GET("search") - suspend fun getGameData(@Query("keywords") keywords: String, + suspend fun getReedData(@Query("keywords") keywords: String, + @Query("minimumSalary") minimumSalary: String) : Response + + @GET("search") + suspend fun getReedData(@Query("keywords") keywords: String, @Query("locationName") locationName: String, @Query("minimumSalary") minimumSalary: String) : Response + @GET("search") + suspend fun getReedData(@Query("keywords") keywords: String, + @Query("locationName") locationName: String, + @Query("minimumSalary") minimumSalary: String, + @Query("resultsToSkip") resultToSkip: Int) : Response + //instantiate api class companion object{ operator fun invoke() : ReedApi{