diff --git a/app/src/main/java/com/mylloon/mobidl/MainActivity.kt b/app/src/main/java/com/mylloon/mobidl/MainActivity.kt index efe0703..b2ca396 100644 --- a/app/src/main/java/com/mylloon/mobidl/MainActivity.kt +++ b/app/src/main/java/com/mylloon/mobidl/MainActivity.kt @@ -25,9 +25,8 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.gson.Gson import com.google.gson.reflect.TypeToken -import java.util.* -import android.widget.Toast import kotlinx.coroutines.* +import java.util.* class MainActivity : AppCompatActivity() { @@ -35,6 +34,7 @@ class MainActivity : AppCompatActivity() { private var inSettings: Boolean = false // by default your not in settings page private var prefs: SharedPreferences? = null // first run detection private val sharedPref = "com.mylloon.MobiDL" // shared pref name + var timeOfLastToast: Long = System.currentTimeMillis() - 2000 companion object { private var instance: MainActivity? = null @@ -163,7 +163,10 @@ class MainActivity : AppCompatActivity() { val checkedItems = booleanArrayOf( false // get this info from the app files somewhere ) - builder.setMultiChoiceItems(arrayOf(getString(R.string.updateCheck)), checkedItems) { _, _, isChecked -> + builder.setMultiChoiceItems( + arrayOf(getString(R.string.updateCheck)), + checkedItems + ) { _, _, isChecked -> println("Update for ${button?.text.toString()}: " + if (isChecked) "enabled" else "disabled" + "") } builder.setNeutralButton(R.string.cancel) { dialog, _ -> dialog.cancel() } @@ -175,7 +178,11 @@ class MainActivity : AppCompatActivity() { if (password != null) { // call script callScrapper(user, password.toString(), button?.text.toString()) } else { - Toast.makeText(applicationContext, R.string.notConnected, Toast.LENGTH_LONG) + Toast.makeText( + applicationContext, + R.string.notConnected, + Toast.LENGTH_LONG + ) .show() loginPage() } @@ -233,7 +240,13 @@ class MainActivity : AppCompatActivity() { } } - private fun callScrapper(user: String, password: String, app: String, captchaID: String? = null, captchaResponse: String? = null) { + private fun callScrapper( + user: String, + password: String, + app: String, + captchaID: String? = null, + captchaResponse: String? = null + ) { if (ContextCompat.checkSelfPermission(this@MainActivity, Manifest.permission.INTERNET) != PackageManager.PERMISSION_GRANTED ) { @@ -242,39 +255,95 @@ class MainActivity : AppCompatActivity() { this.finishAffinity() } else { runBlocking { // GlobalScope.launch { - val returns: MutableList? = Scraper(user, password, app, captchaID = captchaID, captchaResponse = captchaResponse, debug = true).search() + val returns: MutableList>? = Scraper( + user, + password, + app, + captchaID = captchaID, + captchaResponse = captchaResponse + ).search() if (returns != null) { // if job is needed - if (returns[0] == "errorNotConnected") loginPage() - if (returns[0] == "loginAttemptsExceeded") { - if (returns[1] == "null") callScrapper(user, password, app) + if (returns[0].containsKey("errorNotConnected")) { + Toast.makeText( + instance, + "${getString(R.string.connectionFailed)}\n${getString(R.string.credentialsDeleted)}.", + Toast.LENGTH_LONG + ).show() + loginPage() + } + if (returns[0].containsKey("loginAttemptsExceeded")) { + val returnsCaptchaQuestion = returns[0]["question"] + val returnsCaptchaID = returns[0]["qaConfirmID"] + if (returnsCaptchaQuestion == null) callScrapper(user, password, app) val registeredAnswered: Map = Gson().fromJson( assets.open("captcha.json").bufferedReader() .use { it.readText() }, object : TypeToken>() {}.type ) - for((key, value) in registeredAnswered){ - if (returns[1] == key) return@runBlocking callScrapper(user, password, app, returns[2], value) + for ((key, value) in registeredAnswered) { + if (returnsCaptchaQuestion == key) { + println("${applicationContext().getString(R.string.knownCaptcha)}...") + val now = System.currentTimeMillis() + if ((now - timeOfLastToast) >= 2000) { + Toast.makeText( + applicationContext(), + "${applicationContext().getString(R.string.knownCaptcha)}...", + Toast.LENGTH_SHORT + ) + .show() + timeOfLastToast = now + } + return@runBlocking callScrapper( + user, + password, + app, + returnsCaptchaID, + value + ) + } } val builder: AlertDialog.Builder = AlertDialog.Builder( - instance) + instance + ) builder.setTitle("Captcha") - builder.setMessage(returns[1]) + builder.setMessage(returnsCaptchaQuestion) val input = EditText(instance) input.inputType = InputType.TYPE_CLASS_TEXT builder.setView(input) - builder.setPositiveButton(R.string.validate) { _, _ -> callScrapper(user, password, app, returns[2], input.text.toString()) } + builder.setPositiveButton(R.string.validate) { _, _ -> + callScrapper( + user, + password, + app, + returnsCaptchaID, + input.text.toString() + ) + } builder.setNeutralButton(R.string.cancel) { dialog, _ -> dialog.cancel() } builder.show() - - Toast.makeText(instance, "${getString(R.string.connectionFailed)}\n${getString(R.string.credentialsDeleted)}.", - Toast.LENGTH_LONG).show() } - if (returns[0] == "noSID") + if (returns[0].containsKey("noSID")) Toast.makeText(instance, R.string.noSID, Toast.LENGTH_SHORT).show() - if (returns[0] == "badSID") - Toast.makeText(instance, "${getString(R.string.badSID)}...", Toast.LENGTH_SHORT).show() - if (returns[0] == "noResults") - Toast.makeText(instance, "${getString(R.string.badSID)}...", Toast.LENGTH_SHORT).show() + if (returns[0].containsKey("badSID")) + Toast.makeText( + instance, + "${getString(R.string.badSID)}...", + Toast.LENGTH_SHORT + ).show() + if (returns[0].containsKey("noResults")) + Toast.makeText( + instance, + "${getString(R.string.noResults)}...", + Toast.LENGTH_SHORT + ).show() + if (returns[0].containsKey("gotResults")) { + returns.removeFirst() + Toast.makeText( + instance, + "${returns.size} ${getString(R.string.gotResults)} !", + Toast.LENGTH_SHORT + ).show() + } } } } diff --git a/app/src/main/java/com/mylloon/mobidl/Scraper.kt b/app/src/main/java/com/mylloon/mobidl/Scraper.kt index 5d867f9..52c5503 100644 --- a/app/src/main/java/com/mylloon/mobidl/Scraper.kt +++ b/app/src/main/java/com/mylloon/mobidl/Scraper.kt @@ -1,21 +1,15 @@ package com.mylloon.mobidl -import android.widget.Toast import com.github.kittinunf.fuel.core.FuelManager import com.github.kittinunf.fuel.httpGet import com.github.kittinunf.fuel.httpPost -import com.mylloon.mobidl.MainActivity.Companion.applicationContext -import java.lang.Exception -import java.lang.System.currentTimeMillis import com.github.kittinunf.result.Result.Failure as FuelFailure import com.github.kittinunf.result.Result.Success as FuelSuccess -var timeOfLastToast: Long = currentTimeMillis() - 2000 class Scraper( private var pseudo: String, private var password: String, private var app: String, - private var debug: Boolean = false, private var captchaID: String? = null, private var captchaResponse: String? = null ) { @@ -26,17 +20,11 @@ class Scraper( private var errorNotConnected = "Log me on automatically each visit" private var loginAttemptsExceeded = "You exceeded the maximum allowed number of login attempts." - private var searchNotLogged = "Sorry but you are not permitted to use the search system. If you're not logged in please" + private var searchNotLogged = + "Sorry but you are not permitted to use the search system. If you're not logged in please" - private fun errorFormat( // Pretty error message. - code: Int? = null, - message: String = "" - ): String { - return "${if (code != null) "[$code]" else ""}${if ((message.isNotEmpty()) and (code is Int)) " " else ""}$message." - } - - private fun connect(): MutableList? { // Login to the forum using credentials. - if (debug) println("Connection attempt...") + private fun connect(): MutableList>? { // Login to the forum using credentials. + println("Connection attempt...") retry++ FuelManager.instance.basePath = url FuelManager.instance.baseParams = listOf("mode" to "login") @@ -51,7 +39,7 @@ class Scraper( when (result) { is FuelFailure -> { val ex = result.getException() - println(errorFormat(response.statusCode, "Exception: $ex")) + println("[${response.statusCode}] Exception: $ex") } is FuelSuccess -> { println("") // does nothing but is required otherwise everything bugs :( @@ -65,53 +53,50 @@ class Scraper( return search() } - private fun error(htmlPage: String): MutableList? { // Handle connection errors - var message = "" - var array: MutableList? = null - var lengthTime = Toast.LENGTH_LONG + private fun error(htmlPage: String): MutableList>? { // Handle connection errors + var array: MutableList>? = null if (errorNotConnected in htmlPage) { if (loginAttemptsExceeded in htmlPage) { - message = "${applicationContext().getString(R.string.knownCaptcha)}..." - lengthTime = Toast.LENGTH_SHORT - val qaConfirmID = "(?<=qa_confirm_id\" value=\")(.{32})".toRegex().find(htmlPage)?.value + val qaConfirmID = + "(?<=qa_confirm_id\" value=\")(.{32})".toRegex().find(htmlPage)?.value var question = "(?<=answer\">)(.*)= 2000) sendToast() return array } - fun search(): MutableList? { // Do the research. + fun search(): MutableList>? { // Do the research. if (sid == null) { - println(errorFormat(message = "SID not found")) - return if (retry < 3) connect() else mutableListOf("noSID") + println("SID not found") + return if (retry < 3) connect() else mutableListOf(mutableMapOf("noSID" to null)) } else { - if (debug) println("SID recovered") + println("SID recovered") retry = 0 } - if (debug) println("Going to search page...") + println("Going to search page...") FuelManager.instance.basePath = url - FuelManager.instance.baseParams = listOf("sid" to sid, "sr" to "topics", "sf" to "titleonly") + FuelManager.instance.baseParams = + listOf("sid" to sid, "sr" to "topics", "sf" to "titleonly") val session = "/search.php" .httpGet(listOf("keywords" to app)) .responseString { _, response, result -> when (result) { is FuelFailure -> { val ex = result.getException() - println(errorFormat(response.statusCode, "Exception: $ex")) + println("[${response.statusCode}] Exception: $ex") } is FuelSuccess -> { println("") // does nothing but is required otherwise everything bugs :( @@ -120,50 +105,57 @@ class Scraper( } val data = session.join().data.decodeToString() return if (searchNotLogged in data) { - println(errorFormat(message = "The SID doesn't work, new attempt...")) - if (retry < 3) connect() else mutableListOf("badSID") + println("The SID doesn't work, new attempt...") + if (retry < 3) connect() else mutableListOf(mutableMapOf("badSID" to null)) } else parse(data) } - private fun parse(htmlPage: String): MutableList? { // Parse HTML response to a clean list. - if (debug) println("Fetching results for $app...") + private fun parse(htmlPage: String): MutableList> { // Parse HTML response to a clean list. + println("Fetching results for $app...") // println(htmlPage) - if ("No suitable matches were found." in htmlPage) return mutableListOf("noResults") + if ("No suitable matches were found." in htmlPage) return mutableListOf(mutableMapOf("noResults" to null)) val elements: MutableList = htmlPage.split("\n").toMutableList() val finalElements = mutableListOf>() elements.removeFirst() val lastIndex = elements.toList().lastIndex elements[lastIndex] = elements[lastIndex].split("\n")[0] + finalElements.add(0, mapOf("gotResults" to null)) for (i in elements.indices) { - var title: String? - var author: String? - var link: String? - var data: String? - title = try { - Regex(" ?& ?").replace("", Regex("""class="topictitle">(.*)""").find(elements[i])?.value!!) + val title: String? = try { + Regex("""class="topictitle">(.*)""").findAll(elements[i]) + .map { it.groupValues[1] }.toList()[0].replace(Regex("( ?& ?)"), " & ") } catch (e: Exception) { null } - author = try { - val regex = """(
|)\n\n? by (.*)""" - Regex(regex).find(elements[i])?.value!! + val author: String? = try { + val regex = + """(
|)\n\n? by (.*)""" + Regex(regex).findAll(elements[i]).map { it.groupValues.last() }.toList()[0] } catch (e: Exception) { null } - link = try { - Regex("""\./viewtopic\.php\?f=(\d*)&t=(\d*)&""").find(elements[i])?.value!! + val link: String? = try { + val temp = + Regex("""\./viewtopic\.php\?f=(\d*)&t=(\d*)&""").findAll(elements[i]) + "/viewtopic.php?f=${ + temp.map { it.groupValues[1] }.toList()[0] + }&t=${temp.map { it.groupValues[2] }.toList()[0]}" } catch (e: Exception) { null } - data = try { - Regex(""" (.*)""").find(elements[i])?.value!! + val date: String? = try { + Regex(""" (.*)""").findAll(elements[i]) + .map { it.groupValues[1] }.toList()[0] } catch (e: Exception) { null } - finalElements.add(i, mapOf("title" to title, "author" to author, "link" to link, "data" to data)) + finalElements.add( + i + 1, + mapOf("title" to title, "author" to author, "link" to link, "date" to date) + ) } - println(finalElements) - return null + println("Search parsed!") + return finalElements } } diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index cc97248..78862b7 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -26,4 +26,6 @@ Aucun SID n\'a été trouvé Le SID ne fonctionne pas, nouvel essaie Vérification des mises à jour + Aucun résultat n\'a été trouvé + résultats ont été trouvés \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c437ec5..c918556 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -33,4 +33,6 @@ Resolving captchas No SID was found The SID does not work, new attempt + No results were found + results were found \ No newline at end of file