From f7ed0bf458186d30ebfabc12e62ec2ba904d55dc Mon Sep 17 00:00:00 2001 From: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> Date: Fri, 17 Jan 2025 21:47:55 +0500 Subject: [PATCH] fix lint --- .../tachiyomi/extension/all/batoto/BatoTo.kt | 1422 +++++++++-------- .../extension/pt/sussyscan/SussyToons.kt | 616 ++++--- .../extension/pt/sussyscan/SussyToonsDto.kt | 5 +- .../extension/vi/doctruyen3q/DocTruyen3Q.kt | 80 +- .../extension/vi/toptruyen/TopTruyen.kt | 73 +- 5 files changed, 1245 insertions(+), 951 deletions(-) diff --git a/src/all/batoto/src/eu/kanade/tachiyomi/extension/all/batoto/BatoTo.kt b/src/all/batoto/src/eu/kanade/tachiyomi/extension/all/batoto/BatoTo.kt index 6ef3e80c90b..a8e7ae91974 100644 --- a/src/all/batoto/src/eu/kanade/tachiyomi/extension/all/batoto/BatoTo.kt +++ b/src/all/batoto/src/eu/kanade/tachiyomi/extension/all/batoto/BatoTo.kt @@ -45,50 +45,56 @@ import java.util.Locale open class BatoTo( final override val lang: String, private val siteLang: String, -) : ConfigurableSource, ParsedHttpSource() { - +) : ParsedHttpSource(), + ConfigurableSource { private val preferences: SharedPreferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000) + Injekt + .get() + .getSharedPreferences("source_$id", 0x0000) .migrateMirrorPref() } override val name: String = "Bato.to" override val baseUrl: String get() = mirror - override val id: Long = when (lang) { - "zh-Hans" -> 2818874445640189582 - "zh-Hant" -> 38886079663327225 - "ro-MD" -> 8871355786189601023 - else -> super.id - } + override val id: Long = + when (lang) { + "zh-Hans" -> 2818874445640189582 + "zh-Hant" -> 38886079663327225 + "ro-MD" -> 8871355786189601023 + else -> super.id + } override fun setupPreferenceScreen(screen: PreferenceScreen) { - val mirrorPref = ListPreference(screen.context).apply { - key = "${MIRROR_PREF_KEY}_$lang" - title = MIRROR_PREF_TITLE - entries = MIRROR_PREF_ENTRIES - entryValues = MIRROR_PREF_ENTRY_VALUES - setDefaultValue(MIRROR_PREF_DEFAULT_VALUE) - summary = "%s" - setOnPreferenceChangeListener { _, newValue -> - mirror = newValue as String - true + val mirrorPref = + ListPreference(screen.context).apply { + key = "${MIRROR_PREF_KEY}_$lang" + title = MIRROR_PREF_TITLE + entries = MIRROR_PREF_ENTRIES + entryValues = MIRROR_PREF_ENTRY_VALUES + setDefaultValue(MIRROR_PREF_DEFAULT_VALUE) + summary = "%s" + setOnPreferenceChangeListener { _, newValue -> + mirror = newValue as String + true + } + } + val altChapterListPref = + CheckBoxPreference(screen.context).apply { + key = "${ALT_CHAPTER_LIST_PREF_KEY}_$lang" + title = ALT_CHAPTER_LIST_PREF_TITLE + summary = ALT_CHAPTER_LIST_PREF_SUMMARY + setDefaultValue(ALT_CHAPTER_LIST_PREF_DEFAULT_VALUE) + } + val removeOfficialPref = + CheckBoxPreference(screen.context).apply { + key = "${REMOVE_TITLE_VERSION_PREF}_$lang" + title = "Remove version information from entry titles" + summary = "This removes version tags like '(Official)' or '(Yaoi)' from entry titles " + + "and helps identify duplicate entries in your library. " + + "To update existing entries, remove them from your library (unfavorite) and refresh manually. " + + "You might also want to clear the database in advanced settings." + setDefaultValue(false) } - } - val altChapterListPref = CheckBoxPreference(screen.context).apply { - key = "${ALT_CHAPTER_LIST_PREF_KEY}_$lang" - title = ALT_CHAPTER_LIST_PREF_TITLE - summary = ALT_CHAPTER_LIST_PREF_SUMMARY - setDefaultValue(ALT_CHAPTER_LIST_PREF_DEFAULT_VALUE) - } - val removeOfficialPref = CheckBoxPreference(screen.context).apply { - key = "${REMOVE_TITLE_VERSION_PREF}_$lang" - title = "Remove version information from entry titles" - summary = "This removes version tags like '(Official)' or '(Yaoi)' from entry titles " + - "and helps identify duplicate entries in your library. " + - "To update existing entries, remove them from your library (unfavorite) and refresh manually. " + - "You might also want to clear the database in advanced settings." - setDefaultValue(false) - } screen.addPreference(mirrorPref) screen.addPreference(altChapterListPref) screen.addPreference(removeOfficialPref) @@ -105,10 +111,11 @@ open class BatoTo( } private fun getMirrorPref(): String? = preferences.getString("${MIRROR_PREF_KEY}_$lang", MIRROR_PREF_DEFAULT_VALUE) - private fun getAltChapterListPref(): Boolean = preferences.getBoolean("${ALT_CHAPTER_LIST_PREF_KEY}_$lang", ALT_CHAPTER_LIST_PREF_DEFAULT_VALUE) - private fun isRemoveTitleVersion(): Boolean { - return preferences.getBoolean("${REMOVE_TITLE_VERSION_PREF}_$lang", false) - } + + private fun getAltChapterListPref(): Boolean = + preferences.getBoolean("${ALT_CHAPTER_LIST_PREF_KEY}_$lang", ALT_CHAPTER_LIST_PREF_DEFAULT_VALUE) + + private fun isRemoveTitleVersion(): Boolean = preferences.getBoolean("${REMOVE_TITLE_VERSION_PREF}_$lang", false) private fun SharedPreferences.migrateMirrorPref(): SharedPreferences { val selectedMirror = getString("${MIRROR_PREF_KEY}_$lang", MIRROR_PREF_DEFAULT_VALUE)!! @@ -124,17 +131,14 @@ open class BatoTo( private val json: Json by injectLazy() override val client = network.cloudflareClient - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/browse?langs=$siteLang&sort=update&page=$page", headers) - } + override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/browse?langs=$siteLang&sort=update&page=$page", headers) - override fun latestUpdatesSelector(): String { - return when (siteLang) { + override fun latestUpdatesSelector(): String = + when (siteLang) { "" -> "div#series-list div.col" "en,en_us" -> "div#series-list div.col.no-flag" else -> "div#series-list div.col:has([data-lang=\"$siteLang\"])" } - } override fun latestUpdatesFromElement(element: Element): SManga { val manga = SManga.create() @@ -148,9 +152,7 @@ open class BatoTo( override fun latestUpdatesNextPageSelector() = "div#mainer nav.d-none .pagination .page-item:last-of-type:not(.disabled)" - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/browse?langs=$siteLang&sort=views_a&page=$page", headers) - } + override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/browse?langs=$siteLang&sort=views_a&page=$page", headers) override fun popularMangaSelector() = latestUpdatesSelector() @@ -158,19 +160,28 @@ open class BatoTo( override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector() - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList, + ): Observable { return when { query.startsWith("ID:") -> { val id = query.substringAfter("ID:") - client.newCall(GET("$baseUrl/series/$id", headers)).asObservableSuccess() + client + .newCall(GET("$baseUrl/series/$id", headers)) + .asObservableSuccess() .map { response -> queryIDParse(response) } } query.isNotBlank() -> { - val url = "$baseUrl/search".toHttpUrl().newBuilder() - .addQueryParameter("word", query) - .addQueryParameter("page", page.toString()) + val url = + "$baseUrl/search" + .toHttpUrl() + .newBuilder() + .addQueryParameter("word", query) + .addQueryParameter("page", page.toString()) filters.forEach { filter -> when (filter) { is LetterFilter -> { @@ -181,7 +192,9 @@ open class BatoTo( else -> { /* Do Nothing */ } } } - client.newCall(GET(url.build(), headers)).asObservableSuccess() + client + .newCall(GET(url.build(), headers)) + .asObservableSuccess() .map { response -> queryParse(response) } @@ -195,7 +208,9 @@ open class BatoTo( is UtilsFilter -> { if (filter.state != 0) { val filterUrl = "$baseUrl/_utils/comic-list?type=${filter.selected}" - return client.newCall(GET(filterUrl, headers)).asObservableSuccess() + return client + .newCall(GET(filterUrl, headers)) + .asObservableSuccess() .map { response -> queryUtilsParse(response) } @@ -204,7 +219,9 @@ open class BatoTo( is HistoryFilter -> { if (filter.state != 0) { val filterUrl = "$baseUrl/ajax.my.${filter.selected}.paging" - return client.newCall(POST(filterUrl, headers, formBuilder().build())).asObservableSuccess() + return client + .newCall(POST(filterUrl, headers, formBuilder().build())) + .asObservableSuccess() .map { response -> queryHistoryParse(response) } @@ -230,10 +247,11 @@ open class BatoTo( is SortFilter -> { if (filter.state != null) { val sort = getSortFilter()[filter.state!!.index].value - val value = when (filter.state!!.ascending) { - true -> "az" - false -> "za" - } + val value = + when (filter.state!!.ascending) { + true -> "az" + false -> "za" + } url.addQueryParameter("sort", "$sort.$value") } } @@ -253,7 +271,9 @@ open class BatoTo( url.addQueryParameter("chapters", "$min-$max") } - client.newCall(GET(url.build(), headers)).asObservableSuccess() + client + .newCall(GET(url.build(), headers)) + .asObservableSuccess() .map { response -> queryParse(response) } @@ -266,24 +286,30 @@ open class BatoTo( val infoElement = document.select("div#mainer div.container-fluid") val manga = SManga.create() manga.title = infoElement.select("h3").text().removeEntities() - manga.thumbnail_url = document.select("div.attr-cover img") - .attr("abs:src") + manga.thumbnail_url = + document + .select("div.attr-cover img") + .attr("abs:src") manga.url = infoElement.select("h3 a").attr("abs:href") return MangasPage(listOf(manga), false) } private fun queryParse(response: Response): MangasPage { val document = response.asJsoup() - val mangas = document.select(latestUpdatesSelector()) - .map { element -> latestUpdatesFromElement(element) } + val mangas = + document + .select(latestUpdatesSelector()) + .map { element -> latestUpdatesFromElement(element) } val nextPage = document.select(latestUpdatesNextPageSelector()).first() != null return MangasPage(mangas, nextPage) } private fun queryUtilsParse(response: Response): MangasPage { val document = response.asJsoup() - val mangas = document.select("tbody > tr") - .map { element -> searchUtilsFromElement(element) } + val mangas = + document + .select("tbody > tr") + .map { element -> searchUtilsFromElement(element) } return MangasPage(mangas, false) } @@ -292,8 +318,10 @@ open class BatoTo( val html = json.jsonObject["html"]!!.jsonPrimitive.content val document = Jsoup.parse(html, response.request.url.toString()) - val mangas = document.select(".my-history-item") - .map { element -> searchHistoryFromElement(element) } + val mangas = + document + .select(".my-history-item") + .map { element -> searchHistoryFromElement(element) } return MangasPage(mangas, false) } @@ -313,16 +341,24 @@ open class BatoTo( return manga } - open fun formBuilder() = FormBody.Builder().apply { - add("_where", "browse") - add("first", "0") - add("limit", "0") - add("prevPos", "null") - } + open fun formBuilder() = + FormBody.Builder().apply { + add("_where", "browse") + add("first", "0") + add("limit", "0") + add("prevPos", "null") + } + + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request = throw UnsupportedOperationException() - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException() override fun searchMangaSelector() = throw UnsupportedOperationException() + override fun searchMangaFromElement(element: Element) = throw UnsupportedOperationException() + override fun searchMangaNextPageSelector() = throw UnsupportedOperationException() override fun mangaDetailsRequest(manga: SManga): Request { @@ -331,8 +367,12 @@ open class BatoTo( } return super.mangaDetailsRequest(manga) } + private var titleRegex: Regex = - Regex("\\([^()]*\\)|\\{[^{}]*\\}|\\[(?:(?!]).)*]|«[^»]*»|〘[^〙]*〙|「[^」]*」|『[^』]*』|≪[^≫]*≫|﹛[^﹜]*﹜|〖[^〖〗]*〗|𖤍.+?𖤍|《[^》]*》|⌜.+?⌝|⟨[^⟩]*⟩|\\/Official|\\/ Official", RegexOption.IGNORE_CASE) + Regex( + "\\([^()]*\\)|\\{[^{}]*\\}|\\[(?:(?!]).)*]|«[^»]*»|〘[^〙]*〙|「[^」]*」|『[^』]*』|≪[^≫]*≫|﹛[^﹜]*﹜|〖[^〖〗]*〗|𖤍.+?𖤍|《[^》]*》|⌜.+?⌝|⟨[^⟩]*⟩|\\/Official|\\/ Official", + RegexOption.IGNORE_CASE, + ) override fun mangaDetailsParse(document: Document): SManga { val infoElement = document.selectFirst("div#mainer div.container-fluid")!! @@ -340,25 +380,27 @@ open class BatoTo( val workStatus = infoElement.select("div.attr-item:contains(original work) span").text() val uploadStatus = infoElement.select("div.attr-item:contains(upload status) span").text() val originalTitle = infoElement.select("h3").text().removeEntities() - val description = buildString { - append(infoElement.select("div.limit-html").text()) - infoElement.selectFirst(".episode-list > .alert-warning")?.also { - append("\n\n${it.text()}") - } - infoElement.selectFirst("h5:containsOwn(Extra Info:) + div")?.also { - append("\n\nExtra Info:\n${it.text()}") - } - document.selectFirst("div.pb-2.alias-set.line-b-f")?.also { - append("\n\nAlternative Titles:\n") - append(it.text().split('/').joinToString("\n") { "• ${it.trim()}" }) + val description = + buildString { + append(infoElement.select("div.limit-html").text()) + infoElement.selectFirst(".episode-list > .alert-warning")?.also { + append("\n\n${it.text()}") + } + infoElement.selectFirst("h5:containsOwn(Extra Info:) + div")?.also { + append("\n\nExtra Info:\n${it.text()}") + } + document.selectFirst("div.pb-2.alias-set.line-b-f")?.also { + append("\n\nAlternative Titles:\n") + append(it.text().split('/').joinToString("\n") { "• ${it.trim()}" }) + } } - } - val cleanedTitle = if (isRemoveTitleVersion()) { - originalTitle.replace(titleRegex, "").trim() - } else { - originalTitle - } + val cleanedTitle = + if (isRemoveTitleVersion()) { + originalTitle.replace(titleRegex, "").trim() + } else { + originalTitle + } manga.title = cleanedTitle manga.author = infoElement.select("div.attr-item:contains(author) span").text() @@ -369,45 +411,58 @@ open class BatoTo( manga.thumbnail_url = document.select("div.attr-cover img").attr("abs:src") return manga } - private fun parseStatus(workStatus: String?, uploadStatus: String?) = when { + + private fun parseStatus( + workStatus: String?, + uploadStatus: String?, + ) = when { workStatus == null -> SManga.UNKNOWN workStatus.contains("Ongoing") -> SManga.ONGOING workStatus.contains("Cancelled") -> SManga.CANCELLED workStatus.contains("Hiatus") -> SManga.ON_HIATUS - workStatus.contains("Completed") -> when { - uploadStatus?.contains("Ongoing") == true -> SManga.PUBLISHING_FINISHED - else -> SManga.COMPLETED - } + workStatus.contains("Completed") -> + when { + uploadStatus?.contains("Ongoing") == true -> SManga.PUBLISHING_FINISHED + else -> SManga.COMPLETED + } else -> SManga.UNKNOWN } - private fun altChapterParse(response: Response): List { - return Jsoup.parse(response.body.string(), response.request.url.toString(), Parser.xmlParser()) - .select("channel > item").map { item -> + private fun altChapterParse(response: Response): List = + Jsoup + .parse(response.body.string(), response.request.url.toString(), Parser.xmlParser()) + .select("channel > item") + .map { item -> SChapter.create().apply { url = item.selectFirst("guid")!!.text() name = item.selectFirst("title")!!.text() date_upload = parseAltChapterDate(item.selectFirst("pubDate")!!.text()) } } - } private val altDateFormat = SimpleDateFormat("E, dd MMM yyyy H:m:s Z", Locale.US) - private fun parseAltChapterDate(date: String): Long { - return try { + + private fun parseAltChapterDate(date: String): Long = + try { altDateFormat.parse(date)!!.time } catch (_: ParseException) { 0L } - } - private fun checkChapterLists(document: Document): Boolean { - return document.select(".episode-list > .alert-warning").text().contains("This comic has been marked as deleted and the chapter list is not available.") - } + private fun checkChapterLists(document: Document): Boolean = + document + .select( + ".episode-list > .alert-warning", + ).text() + .contains("This comic has been marked as deleted and the chapter list is not available.") - override fun chapterListRequest(manga: SManga): Request { - return if (getAltChapterListPref()) { - val id = manga.url.substringBeforeLast("/").substringAfterLast("/").trim() + override fun chapterListRequest(manga: SManga): Request = + if (getAltChapterListPref()) { + val id = + manga.url + .substringBeforeLast("/") + .substringAfterLast("/") + .trim() GET("$baseUrl/rss/series/$id.xml", headers) } else if (manga.url.startsWith("http")) { @@ -415,7 +470,6 @@ open class BatoTo( } else { super.chapterListRequest(manga) } - } override fun chapterListParse(response: Response): List { if (getAltChapterListPref()) { @@ -428,7 +482,8 @@ open class BatoTo( throw Exception("Deleted from site") } - return document.select(chapterListSelector()) + return document + .select(chapterListSelector()) .map(::chapterFromElement) } @@ -442,11 +497,12 @@ open class BatoTo( val time = element.select("div.extra > i.ps-3").text() chapter.setUrlWithoutDomain(urlElement.attr("href")) chapter.name = urlElement.text() - chapter.scanlator = when { - group.isNotBlank() -> group - user.isNotBlank() -> user - else -> "Unknown" - } + chapter.scanlator = + when { + group.isNotBlank() -> group + user.isNotBlank() -> user + else -> "Unknown" + } if (time != "") { chapter.date_upload = parseChapterDate(time) } @@ -457,48 +513,90 @@ open class BatoTo( val value = date.split(' ')[0].toInt() return when { - "secs" in date -> Calendar.getInstance().apply { - add(Calendar.SECOND, -value) - }.timeInMillis - "mins" in date -> Calendar.getInstance().apply { - add(Calendar.MINUTE, -value) - }.timeInMillis - "hours" in date -> Calendar.getInstance().apply { - add(Calendar.HOUR_OF_DAY, -value) - }.timeInMillis - "days" in date -> Calendar.getInstance().apply { - add(Calendar.DATE, -value) - }.timeInMillis - "weeks" in date -> Calendar.getInstance().apply { - add(Calendar.DATE, -value * 7) - }.timeInMillis - "months" in date -> Calendar.getInstance().apply { - add(Calendar.MONTH, -value) - }.timeInMillis - "years" in date -> Calendar.getInstance().apply { - add(Calendar.YEAR, -value) - }.timeInMillis - "sec" in date -> Calendar.getInstance().apply { - add(Calendar.SECOND, -value) - }.timeInMillis - "min" in date -> Calendar.getInstance().apply { - add(Calendar.MINUTE, -value) - }.timeInMillis - "hour" in date -> Calendar.getInstance().apply { - add(Calendar.HOUR_OF_DAY, -value) - }.timeInMillis - "day" in date -> Calendar.getInstance().apply { - add(Calendar.DATE, -value) - }.timeInMillis - "week" in date -> Calendar.getInstance().apply { - add(Calendar.DATE, -value * 7) - }.timeInMillis - "month" in date -> Calendar.getInstance().apply { - add(Calendar.MONTH, -value) - }.timeInMillis - "year" in date -> Calendar.getInstance().apply { - add(Calendar.YEAR, -value) - }.timeInMillis + "secs" in date -> + Calendar + .getInstance() + .apply { + add(Calendar.SECOND, -value) + }.timeInMillis + "mins" in date -> + Calendar + .getInstance() + .apply { + add(Calendar.MINUTE, -value) + }.timeInMillis + "hours" in date -> + Calendar + .getInstance() + .apply { + add(Calendar.HOUR_OF_DAY, -value) + }.timeInMillis + "days" in date -> + Calendar + .getInstance() + .apply { + add(Calendar.DATE, -value) + }.timeInMillis + "weeks" in date -> + Calendar + .getInstance() + .apply { + add(Calendar.DATE, -value * 7) + }.timeInMillis + "months" in date -> + Calendar + .getInstance() + .apply { + add(Calendar.MONTH, -value) + }.timeInMillis + "years" in date -> + Calendar + .getInstance() + .apply { + add(Calendar.YEAR, -value) + }.timeInMillis + "sec" in date -> + Calendar + .getInstance() + .apply { + add(Calendar.SECOND, -value) + }.timeInMillis + "min" in date -> + Calendar + .getInstance() + .apply { + add(Calendar.MINUTE, -value) + }.timeInMillis + "hour" in date -> + Calendar + .getInstance() + .apply { + add(Calendar.HOUR_OF_DAY, -value) + }.timeInMillis + "day" in date -> + Calendar + .getInstance() + .apply { + add(Calendar.DATE, -value) + }.timeInMillis + "week" in date -> + Calendar + .getInstance() + .apply { + add(Calendar.DATE, -value * 7) + }.timeInMillis + "month" in date -> + Calendar + .getInstance() + .apply { + add(Calendar.MONTH, -value) + }.timeInMillis + "year" in date -> + Calendar + .getInstance() + .apply { + add(Calendar.YEAR, -value) + }.timeInMillis else -> { return 0 } @@ -513,8 +611,9 @@ open class BatoTo( } override fun pageListParse(document: Document): List { - val script = document.selectFirst("script:containsData(imgHttps):containsData(batoWord):containsData(batoPass)")?.html() - ?: throw RuntimeException("Couldn't find script with image data.") + val script = + document.selectFirst("script:containsData(imgHttps):containsData(batoWord):containsData(batoPass)")?.html() + ?: throw RuntimeException("Couldn't find script with image data.") val imgHttpsString = script.substringAfter("const imgHttps =").substringBefore(";").trim() val imageUrls = json.parseToJsonElement(imgHttpsString).jsonArray.map { it.jsonPrimitive.content } @@ -527,11 +626,12 @@ open class BatoTo( return imageUrls.mapIndexed { i, it -> val acc = imgAccList.getOrNull(i) - val url = if (acc != null) { - "$it?$acc" - } else { - it - } + val url = + if (acc != null) { + "$it?$acc" + } else { + it + } Page(i, imageUrl = url) } @@ -541,40 +641,72 @@ open class BatoTo( private fun String.removeEntities(): String = Parser.unescapeEntities(this, true) - override fun getFilterList() = FilterList( - LetterFilter(getLetterFilter(), 0), - Filter.Separator(), - Filter.Header("NOTE: Ignored if using text search!"), - Filter.Separator(), - SortFilter(getSortFilter().map { it.name }.toTypedArray()), - StatusFilter(getStatusFilter(), 0), - GenreGroupFilter(getGenreFilter()), - OriginGroupFilter(getOrginFilter()), - LangGroupFilter(getLangFilter()), - MinChapterTextFilter(), - MaxChapterTextFilter(), - Filter.Separator(), - Filter.Header("NOTE: Filters below are incompatible with any other filters!"), - Filter.Header("NOTE: Login Required!"), - Filter.Separator(), - UtilsFilter(getUtilsFilter(), 0), - HistoryFilter(getHistoryFilter(), 0), + override fun getFilterList() = + FilterList( + LetterFilter(getLetterFilter(), 0), + Filter.Separator(), + Filter.Header("NOTE: Ignored if using text search!"), + Filter.Separator(), + SortFilter(getSortFilter().map { it.name }.toTypedArray()), + StatusFilter(getStatusFilter(), 0), + GenreGroupFilter(getGenreFilter()), + OriginGroupFilter(getOrginFilter()), + LangGroupFilter(getLangFilter()), + MinChapterTextFilter(), + MaxChapterTextFilter(), + Filter.Separator(), + Filter.Header("NOTE: Filters below are incompatible with any other filters!"), + Filter.Header("NOTE: Login Required!"), + Filter.Separator(), + UtilsFilter(getUtilsFilter(), 0), + HistoryFilter(getHistoryFilter(), 0), + ) + + class SelectFilterOption( + val name: String, + val value: String, ) - class SelectFilterOption(val name: String, val value: String) - class CheckboxFilterOption(val value: String, name: String, default: Boolean = false) : Filter.CheckBox(name, default) - class TriStateFilterOption(val value: String, name: String, default: Int = 0) : Filter.TriState(name, default) - abstract class SelectFilter(name: String, private val options: List, default: Int = 0) : Filter.Select(name, options.map { it.name }.toTypedArray(), default) { + class CheckboxFilterOption( + val value: String, + name: String, + default: Boolean = false, + ) : Filter.CheckBox(name, default) + + class TriStateFilterOption( + val value: String, + name: String, + default: Int = 0, + ) : Filter.TriState(name, default) + + abstract class SelectFilter( + name: String, + private val options: List, + default: Int = 0, + ) : Filter.Select( + name, + options + .map { + it.name + }.toTypedArray(), + default, + ) { val selected: String get() = options[state].value } - abstract class CheckboxGroupFilter(name: String, options: List) : Filter.Group(name, options) { + abstract class CheckboxGroupFilter( + name: String, + options: List, + ) : Filter.Group(name, options) { val selected: List get() = state.filter { it.state }.map { it.value } } - abstract class TriStateGroupFilter(name: String, options: List) : Filter.Group(name, options) { + abstract class TriStateGroupFilter( + name: String, + options: List, + ) : Filter.Group(name, options) { val included: List get() = state.filter { it.isIncluded() }.map { it.value } @@ -582,458 +714,496 @@ open class BatoTo( get() = state.filter { it.isExcluded() }.map { it.value } } - abstract class TextFilter(name: String) : Filter.Text(name) + abstract class TextFilter( + name: String, + ) : Filter.Text(name) + + class SortFilter( + sortables: Array, + ) : Filter.Sort("Sort", sortables, Selection(5, false)) + + class StatusFilter( + options: List, + default: Int, + ) : SelectFilter("Status", options, default) + + class OriginGroupFilter( + options: List, + ) : CheckboxGroupFilter("Origin", options) + + class GenreGroupFilter( + options: List, + ) : TriStateGroupFilter("Genre", options) - class SortFilter(sortables: Array) : Filter.Sort("Sort", sortables, Selection(5, false)) - class StatusFilter(options: List, default: Int) : SelectFilter("Status", options, default) - class OriginGroupFilter(options: List) : CheckboxGroupFilter("Origin", options) - class GenreGroupFilter(options: List) : TriStateGroupFilter("Genre", options) class MinChapterTextFilter : TextFilter("Min. Chapters") + class MaxChapterTextFilter : TextFilter("Max. Chapters") - class LangGroupFilter(options: List) : CheckboxGroupFilter("Languages", options) - class LetterFilter(options: List, default: Int) : SelectFilter("Letter matching mode (Slow)", options, default) - class UtilsFilter(options: List, default: Int) : SelectFilter("Utils comic list", options, default) - class HistoryFilter(options: List, default: Int) : SelectFilter("Personal list", options, default) - - private fun getLetterFilter() = listOf( - SelectFilterOption("Disabled", "disabled"), - SelectFilterOption("Enabled", "enabled"), - ) - private fun getSortFilter() = listOf( - SelectFilterOption("Z-A", "title"), - SelectFilterOption("Last Updated", "update"), - SelectFilterOption("Newest Added", "create"), - SelectFilterOption("Most Views Totally", "views_a"), - SelectFilterOption("Most Views 365 days", "views_y"), - SelectFilterOption("Most Views 30 days", "views_m"), - SelectFilterOption("Most Views 7 days", "views_w"), - SelectFilterOption("Most Views 24 hours", "views_d"), - SelectFilterOption("Most Views 60 minutes", "views_h"), - ) + class LangGroupFilter( + options: List, + ) : CheckboxGroupFilter("Languages", options) + + class LetterFilter( + options: List, + default: Int, + ) : SelectFilter("Letter matching mode (Slow)", options, default) + + class UtilsFilter( + options: List, + default: Int, + ) : SelectFilter("Utils comic list", options, default) + + class HistoryFilter( + options: List, + default: Int, + ) : SelectFilter("Personal list", options, default) + + private fun getLetterFilter() = + listOf( + SelectFilterOption("Disabled", "disabled"), + SelectFilterOption("Enabled", "enabled"), + ) - private fun getHistoryFilter() = listOf( - SelectFilterOption("None", ""), - SelectFilterOption("My History", "history"), - SelectFilterOption("My Updates", "updates"), - ) + private fun getSortFilter() = + listOf( + SelectFilterOption("Z-A", "title"), + SelectFilterOption("Last Updated", "update"), + SelectFilterOption("Newest Added", "create"), + SelectFilterOption("Most Views Totally", "views_a"), + SelectFilterOption("Most Views 365 days", "views_y"), + SelectFilterOption("Most Views 30 days", "views_m"), + SelectFilterOption("Most Views 7 days", "views_w"), + SelectFilterOption("Most Views 24 hours", "views_d"), + SelectFilterOption("Most Views 60 minutes", "views_h"), + ) - private fun getUtilsFilter() = listOf( - SelectFilterOption("None", ""), - SelectFilterOption("Comics: I Created", "i-created"), - SelectFilterOption("Comics: I Modified", "i-modified"), - SelectFilterOption("Comics: I Uploaded", "i-uploaded"), - SelectFilterOption("Comics: Authorized to me", "i-authorized"), - SelectFilterOption("Comics: Draft Status", "status-draft"), - SelectFilterOption("Comics: Hidden Status", "status-hidden"), - SelectFilterOption("Ongoing and Not updated in 30-60 days", "not-updated-30-60"), - SelectFilterOption("Ongoing and Not updated in 60-90 days", "not-updated-60-90"), - SelectFilterOption("Ongoing and Not updated in 90-180 days", "not-updated-90-180"), - SelectFilterOption("Ongoing and Not updated in 180-360 days", "not-updated-180-360"), - SelectFilterOption("Ongoing and Not updated in 360-1000 days", "not-updated-360-1000"), - SelectFilterOption("Ongoing and Not updated more than 1000 days", "not-updated-1000"), - ) + private fun getHistoryFilter() = + listOf( + SelectFilterOption("None", ""), + SelectFilterOption("My History", "history"), + SelectFilterOption("My Updates", "updates"), + ) - private fun getStatusFilter() = listOf( - SelectFilterOption("All", ""), - SelectFilterOption("Pending", "pending"), - SelectFilterOption("Ongoing", "ongoing"), - SelectFilterOption("Completed", "completed"), - SelectFilterOption("Hiatus", "hiatus"), - SelectFilterOption("Cancelled", "cancelled"), - ) + private fun getUtilsFilter() = + listOf( + SelectFilterOption("None", ""), + SelectFilterOption("Comics: I Created", "i-created"), + SelectFilterOption("Comics: I Modified", "i-modified"), + SelectFilterOption("Comics: I Uploaded", "i-uploaded"), + SelectFilterOption("Comics: Authorized to me", "i-authorized"), + SelectFilterOption("Comics: Draft Status", "status-draft"), + SelectFilterOption("Comics: Hidden Status", "status-hidden"), + SelectFilterOption("Ongoing and Not updated in 30-60 days", "not-updated-30-60"), + SelectFilterOption("Ongoing and Not updated in 60-90 days", "not-updated-60-90"), + SelectFilterOption("Ongoing and Not updated in 90-180 days", "not-updated-90-180"), + SelectFilterOption("Ongoing and Not updated in 180-360 days", "not-updated-180-360"), + SelectFilterOption("Ongoing and Not updated in 360-1000 days", "not-updated-360-1000"), + SelectFilterOption("Ongoing and Not updated more than 1000 days", "not-updated-1000"), + ) - private fun getOrginFilter() = listOf( - // Values exported from publish.bato.to - CheckboxFilterOption("zh", "Chinese"), - CheckboxFilterOption("en", "English"), - CheckboxFilterOption("ja", "Japanese"), - CheckboxFilterOption("ko", "Korean"), - CheckboxFilterOption("af", "Afrikaans"), - CheckboxFilterOption("sq", "Albanian"), - CheckboxFilterOption("am", "Amharic"), - CheckboxFilterOption("ar", "Arabic"), - CheckboxFilterOption("hy", "Armenian"), - CheckboxFilterOption("az", "Azerbaijani"), - CheckboxFilterOption("be", "Belarusian"), - CheckboxFilterOption("bn", "Bengali"), - CheckboxFilterOption("bs", "Bosnian"), - CheckboxFilterOption("bg", "Bulgarian"), - CheckboxFilterOption("my", "Burmese"), - CheckboxFilterOption("km", "Cambodian"), - CheckboxFilterOption("ca", "Catalan"), - CheckboxFilterOption("ceb", "Cebuano"), - CheckboxFilterOption("zh_hk", "Chinese (Cantonese)"), - CheckboxFilterOption("zh_tw", "Chinese (Traditional)"), - CheckboxFilterOption("hr", "Croatian"), - CheckboxFilterOption("cs", "Czech"), - CheckboxFilterOption("da", "Danish"), - CheckboxFilterOption("nl", "Dutch"), - CheckboxFilterOption("en_us", "English (United States)"), - CheckboxFilterOption("eo", "Esperanto"), - CheckboxFilterOption("et", "Estonian"), - CheckboxFilterOption("fo", "Faroese"), - CheckboxFilterOption("fil", "Filipino"), - CheckboxFilterOption("fi", "Finnish"), - CheckboxFilterOption("fr", "French"), - CheckboxFilterOption("ka", "Georgian"), - CheckboxFilterOption("de", "German"), - CheckboxFilterOption("el", "Greek"), - CheckboxFilterOption("gn", "Guarani"), - CheckboxFilterOption("gu", "Gujarati"), - CheckboxFilterOption("ht", "Haitian Creole"), - CheckboxFilterOption("ha", "Hausa"), - CheckboxFilterOption("he", "Hebrew"), - CheckboxFilterOption("hi", "Hindi"), - CheckboxFilterOption("hu", "Hungarian"), - CheckboxFilterOption("is", "Icelandic"), - CheckboxFilterOption("ig", "Igbo"), - CheckboxFilterOption("id", "Indonesian"), - CheckboxFilterOption("ga", "Irish"), - CheckboxFilterOption("it", "Italian"), - CheckboxFilterOption("jv", "Javanese"), - CheckboxFilterOption("kn", "Kannada"), - CheckboxFilterOption("kk", "Kazakh"), - CheckboxFilterOption("ku", "Kurdish"), - CheckboxFilterOption("ky", "Kyrgyz"), - CheckboxFilterOption("lo", "Laothian"), - CheckboxFilterOption("lv", "Latvian"), - CheckboxFilterOption("lt", "Lithuanian"), - CheckboxFilterOption("lb", "Luxembourgish"), - CheckboxFilterOption("mk", "Macedonian"), - CheckboxFilterOption("mg", "Malagasy"), - CheckboxFilterOption("ms", "Malay"), - CheckboxFilterOption("ml", "Malayalam"), - CheckboxFilterOption("mt", "Maltese"), - CheckboxFilterOption("mi", "Maori"), - CheckboxFilterOption("mr", "Marathi"), - CheckboxFilterOption("mo", "Moldavian"), - CheckboxFilterOption("mn", "Mongolian"), - CheckboxFilterOption("ne", "Nepali"), - CheckboxFilterOption("no", "Norwegian"), - CheckboxFilterOption("ny", "Nyanja"), - CheckboxFilterOption("ps", "Pashto"), - CheckboxFilterOption("fa", "Persian"), - CheckboxFilterOption("pl", "Polish"), - CheckboxFilterOption("pt", "Portuguese"), - CheckboxFilterOption("pt_br", "Portuguese (Brazil)"), - CheckboxFilterOption("ro", "Romanian"), - CheckboxFilterOption("rm", "Romansh"), - CheckboxFilterOption("ru", "Russian"), - CheckboxFilterOption("sm", "Samoan"), - CheckboxFilterOption("sr", "Serbian"), - CheckboxFilterOption("sh", "Serbo-Croatian"), - CheckboxFilterOption("st", "Sesotho"), - CheckboxFilterOption("sn", "Shona"), - CheckboxFilterOption("sd", "Sindhi"), - CheckboxFilterOption("si", "Sinhalese"), - CheckboxFilterOption("sk", "Slovak"), - CheckboxFilterOption("sl", "Slovenian"), - CheckboxFilterOption("so", "Somali"), - CheckboxFilterOption("es", "Spanish"), - CheckboxFilterOption("es_419", "Spanish (Latin America)"), - CheckboxFilterOption("sw", "Swahili"), - CheckboxFilterOption("sv", "Swedish"), - CheckboxFilterOption("tg", "Tajik"), - CheckboxFilterOption("ta", "Tamil"), - CheckboxFilterOption("th", "Thai"), - CheckboxFilterOption("ti", "Tigrinya"), - CheckboxFilterOption("to", "Tonga"), - CheckboxFilterOption("tr", "Turkish"), - CheckboxFilterOption("tk", "Turkmen"), - CheckboxFilterOption("uk", "Ukrainian"), - CheckboxFilterOption("ur", "Urdu"), - CheckboxFilterOption("uz", "Uzbek"), - CheckboxFilterOption("vi", "Vietnamese"), - CheckboxFilterOption("yo", "Yoruba"), - CheckboxFilterOption("zu", "Zulu"), - CheckboxFilterOption("_t", "Other"), - ) + private fun getStatusFilter() = + listOf( + SelectFilterOption("All", ""), + SelectFilterOption("Pending", "pending"), + SelectFilterOption("Ongoing", "ongoing"), + SelectFilterOption("Completed", "completed"), + SelectFilterOption("Hiatus", "hiatus"), + SelectFilterOption("Cancelled", "cancelled"), + ) - private fun getGenreFilter() = listOf( - TriStateFilterOption("artbook", "Artbook"), - TriStateFilterOption("cartoon", "Cartoon"), - TriStateFilterOption("comic", "Comic"), - TriStateFilterOption("doujinshi", "Doujinshi"), - TriStateFilterOption("imageset", "Imageset"), - TriStateFilterOption("manga", "Manga"), - TriStateFilterOption("manhua", "Manhua"), - TriStateFilterOption("manhwa", "Manhwa"), - TriStateFilterOption("webtoon", "Webtoon"), - TriStateFilterOption("western", "Western"), - - TriStateFilterOption("shoujo", "Shoujo(G)"), - TriStateFilterOption("shounen", "Shounen(B)"), - TriStateFilterOption("josei", "Josei(W)"), - TriStateFilterOption("seinen", "Seinen(M)"), - TriStateFilterOption("yuri", "Yuri(GL)"), - TriStateFilterOption("yaoi", "Yaoi(BL)"), - TriStateFilterOption("futa", "Futa(WL)"), - TriStateFilterOption("bara", "Bara(ML)"), - - TriStateFilterOption("gore", "Gore"), - TriStateFilterOption("bloody", "Bloody"), - TriStateFilterOption("violence", "Violence"), - TriStateFilterOption("ecchi", "Ecchi"), - TriStateFilterOption("adult", "Adult"), - TriStateFilterOption("mature", "Mature"), - TriStateFilterOption("smut", "Smut"), - TriStateFilterOption("hentai", "Hentai"), - - TriStateFilterOption("_4_koma", "4-Koma"), - TriStateFilterOption("action", "Action"), - TriStateFilterOption("adaptation", "Adaptation"), - TriStateFilterOption("adventure", "Adventure"), - TriStateFilterOption("age_gap", "Age Gap"), - TriStateFilterOption("aliens", "Aliens"), - TriStateFilterOption("animals", "Animals"), - TriStateFilterOption("anthology", "Anthology"), - TriStateFilterOption("beasts", "Beasts"), - TriStateFilterOption("bodyswap", "Bodyswap"), - TriStateFilterOption("cars", "cars"), - TriStateFilterOption("cheating_infidelity", "Cheating/Infidelity"), - TriStateFilterOption("childhood_friends", "Childhood Friends"), - TriStateFilterOption("college_life", "College Life"), - TriStateFilterOption("comedy", "Comedy"), - TriStateFilterOption("contest_winning", "Contest Winning"), - TriStateFilterOption("cooking", "Cooking"), - TriStateFilterOption("crime", "crime"), - TriStateFilterOption("crossdressing", "Crossdressing"), - TriStateFilterOption("delinquents", "Delinquents"), - TriStateFilterOption("dementia", "Dementia"), - TriStateFilterOption("demons", "Demons"), - TriStateFilterOption("drama", "Drama"), - TriStateFilterOption("dungeons", "Dungeons"), - TriStateFilterOption("emperor_daughte", "Emperor's Daughter"), - TriStateFilterOption("fantasy", "Fantasy"), - TriStateFilterOption("fan_colored", "Fan-Colored"), - TriStateFilterOption("fetish", "Fetish"), - TriStateFilterOption("full_color", "Full Color"), - TriStateFilterOption("game", "Game"), - TriStateFilterOption("gender_bender", "Gender Bender"), - TriStateFilterOption("genderswap", "Genderswap"), - TriStateFilterOption("ghosts", "Ghosts"), - TriStateFilterOption("gyaru", "Gyaru"), - TriStateFilterOption("harem", "Harem"), - TriStateFilterOption("harlequin", "Harlequin"), - TriStateFilterOption("historical", "Historical"), - TriStateFilterOption("horror", "Horror"), - TriStateFilterOption("incest", "Incest"), - TriStateFilterOption("isekai", "Isekai"), - TriStateFilterOption("kids", "Kids"), - TriStateFilterOption("loli", "Loli"), - TriStateFilterOption("magic", "Magic"), - TriStateFilterOption("magical_girls", "Magical Girls"), - TriStateFilterOption("martial_arts", "Martial Arts"), - TriStateFilterOption("mecha", "Mecha"), - TriStateFilterOption("medical", "Medical"), - TriStateFilterOption("military", "Military"), - TriStateFilterOption("monster_girls", "Monster Girls"), - TriStateFilterOption("monsters", "Monsters"), - TriStateFilterOption("music", "Music"), - TriStateFilterOption("mystery", "Mystery"), - TriStateFilterOption("netorare", "Netorare/NTR"), - TriStateFilterOption("ninja", "Ninja"), - TriStateFilterOption("office_workers", "Office Workers"), - TriStateFilterOption("omegaverse", "Omegaverse"), - TriStateFilterOption("oneshot", "Oneshot"), - TriStateFilterOption("parody", "parody"), - TriStateFilterOption("philosophical", "Philosophical"), - TriStateFilterOption("police", "Police"), - TriStateFilterOption("post_apocalyptic", "Post-Apocalyptic"), - TriStateFilterOption("psychological", "Psychological"), - TriStateFilterOption("regression", "Regression"), - TriStateFilterOption("reincarnation", "Reincarnation"), - TriStateFilterOption("reverse_harem", "Reverse Harem"), - TriStateFilterOption("reverse_isekai", "Reverse Isekai"), - TriStateFilterOption("romance", "Romance"), - TriStateFilterOption("royal_family", "Royal Family"), - TriStateFilterOption("royalty", "Royalty"), - TriStateFilterOption("samurai", "Samurai"), - TriStateFilterOption("school_life", "School Life"), - TriStateFilterOption("sci_fi", "Sci-Fi"), - TriStateFilterOption("shota", "Shota"), - TriStateFilterOption("shoujo_ai", "Shoujo Ai"), - TriStateFilterOption("shounen_ai", "Shounen Ai"), - TriStateFilterOption("showbiz", "Showbiz"), - TriStateFilterOption("slice_of_life", "Slice of Life"), - TriStateFilterOption("sm_bdsm", "SM/BDSM/SUB-DOM"), - TriStateFilterOption("space", "Space"), - TriStateFilterOption("sports", "Sports"), - TriStateFilterOption("super_power", "Super Power"), - TriStateFilterOption("superhero", "Superhero"), - TriStateFilterOption("supernatural", "Supernatural"), - TriStateFilterOption("survival", "Survival"), - TriStateFilterOption("thriller", "Thriller"), - TriStateFilterOption("time_travel", "Time Travel"), - TriStateFilterOption("tower_climbing", "Tower Climbing"), - TriStateFilterOption("traditional_games", "Traditional Games"), - TriStateFilterOption("tragedy", "Tragedy"), - TriStateFilterOption("transmigration", "Transmigration"), - TriStateFilterOption("vampires", "Vampires"), - TriStateFilterOption("villainess", "Villainess"), - TriStateFilterOption("video_games", "Video Games"), - TriStateFilterOption("virtual_reality", "Virtual Reality"), - TriStateFilterOption("wuxia", "Wuxia"), - TriStateFilterOption("xianxia", "Xianxia"), - TriStateFilterOption("xuanhuan", "Xuanhuan"), - TriStateFilterOption("zombies", "Zombies"), - // Hidden Genres - TriStateFilterOption("shotacon", "shotacon"), - TriStateFilterOption("lolicon", "lolicon"), - TriStateFilterOption("award_winning", "Award Winning"), - TriStateFilterOption("youkai", "Youkai"), - TriStateFilterOption("uncategorized", "Uncategorized"), - ) + private fun getOrginFilter() = + listOf( + // Values exported from publish.bato.to + CheckboxFilterOption("zh", "Chinese"), + CheckboxFilterOption("en", "English"), + CheckboxFilterOption("ja", "Japanese"), + CheckboxFilterOption("ko", "Korean"), + CheckboxFilterOption("af", "Afrikaans"), + CheckboxFilterOption("sq", "Albanian"), + CheckboxFilterOption("am", "Amharic"), + CheckboxFilterOption("ar", "Arabic"), + CheckboxFilterOption("hy", "Armenian"), + CheckboxFilterOption("az", "Azerbaijani"), + CheckboxFilterOption("be", "Belarusian"), + CheckboxFilterOption("bn", "Bengali"), + CheckboxFilterOption("bs", "Bosnian"), + CheckboxFilterOption("bg", "Bulgarian"), + CheckboxFilterOption("my", "Burmese"), + CheckboxFilterOption("km", "Cambodian"), + CheckboxFilterOption("ca", "Catalan"), + CheckboxFilterOption("ceb", "Cebuano"), + CheckboxFilterOption("zh_hk", "Chinese (Cantonese)"), + CheckboxFilterOption("zh_tw", "Chinese (Traditional)"), + CheckboxFilterOption("hr", "Croatian"), + CheckboxFilterOption("cs", "Czech"), + CheckboxFilterOption("da", "Danish"), + CheckboxFilterOption("nl", "Dutch"), + CheckboxFilterOption("en_us", "English (United States)"), + CheckboxFilterOption("eo", "Esperanto"), + CheckboxFilterOption("et", "Estonian"), + CheckboxFilterOption("fo", "Faroese"), + CheckboxFilterOption("fil", "Filipino"), + CheckboxFilterOption("fi", "Finnish"), + CheckboxFilterOption("fr", "French"), + CheckboxFilterOption("ka", "Georgian"), + CheckboxFilterOption("de", "German"), + CheckboxFilterOption("el", "Greek"), + CheckboxFilterOption("gn", "Guarani"), + CheckboxFilterOption("gu", "Gujarati"), + CheckboxFilterOption("ht", "Haitian Creole"), + CheckboxFilterOption("ha", "Hausa"), + CheckboxFilterOption("he", "Hebrew"), + CheckboxFilterOption("hi", "Hindi"), + CheckboxFilterOption("hu", "Hungarian"), + CheckboxFilterOption("is", "Icelandic"), + CheckboxFilterOption("ig", "Igbo"), + CheckboxFilterOption("id", "Indonesian"), + CheckboxFilterOption("ga", "Irish"), + CheckboxFilterOption("it", "Italian"), + CheckboxFilterOption("jv", "Javanese"), + CheckboxFilterOption("kn", "Kannada"), + CheckboxFilterOption("kk", "Kazakh"), + CheckboxFilterOption("ku", "Kurdish"), + CheckboxFilterOption("ky", "Kyrgyz"), + CheckboxFilterOption("lo", "Laothian"), + CheckboxFilterOption("lv", "Latvian"), + CheckboxFilterOption("lt", "Lithuanian"), + CheckboxFilterOption("lb", "Luxembourgish"), + CheckboxFilterOption("mk", "Macedonian"), + CheckboxFilterOption("mg", "Malagasy"), + CheckboxFilterOption("ms", "Malay"), + CheckboxFilterOption("ml", "Malayalam"), + CheckboxFilterOption("mt", "Maltese"), + CheckboxFilterOption("mi", "Maori"), + CheckboxFilterOption("mr", "Marathi"), + CheckboxFilterOption("mo", "Moldavian"), + CheckboxFilterOption("mn", "Mongolian"), + CheckboxFilterOption("ne", "Nepali"), + CheckboxFilterOption("no", "Norwegian"), + CheckboxFilterOption("ny", "Nyanja"), + CheckboxFilterOption("ps", "Pashto"), + CheckboxFilterOption("fa", "Persian"), + CheckboxFilterOption("pl", "Polish"), + CheckboxFilterOption("pt", "Portuguese"), + CheckboxFilterOption("pt_br", "Portuguese (Brazil)"), + CheckboxFilterOption("ro", "Romanian"), + CheckboxFilterOption("rm", "Romansh"), + CheckboxFilterOption("ru", "Russian"), + CheckboxFilterOption("sm", "Samoan"), + CheckboxFilterOption("sr", "Serbian"), + CheckboxFilterOption("sh", "Serbo-Croatian"), + CheckboxFilterOption("st", "Sesotho"), + CheckboxFilterOption("sn", "Shona"), + CheckboxFilterOption("sd", "Sindhi"), + CheckboxFilterOption("si", "Sinhalese"), + CheckboxFilterOption("sk", "Slovak"), + CheckboxFilterOption("sl", "Slovenian"), + CheckboxFilterOption("so", "Somali"), + CheckboxFilterOption("es", "Spanish"), + CheckboxFilterOption("es_419", "Spanish (Latin America)"), + CheckboxFilterOption("sw", "Swahili"), + CheckboxFilterOption("sv", "Swedish"), + CheckboxFilterOption("tg", "Tajik"), + CheckboxFilterOption("ta", "Tamil"), + CheckboxFilterOption("th", "Thai"), + CheckboxFilterOption("ti", "Tigrinya"), + CheckboxFilterOption("to", "Tonga"), + CheckboxFilterOption("tr", "Turkish"), + CheckboxFilterOption("tk", "Turkmen"), + CheckboxFilterOption("uk", "Ukrainian"), + CheckboxFilterOption("ur", "Urdu"), + CheckboxFilterOption("uz", "Uzbek"), + CheckboxFilterOption("vi", "Vietnamese"), + CheckboxFilterOption("yo", "Yoruba"), + CheckboxFilterOption("zu", "Zulu"), + CheckboxFilterOption("_t", "Other"), + ) - private fun getLangFilter() = listOf( - // Values exported from publish.bato.to - CheckboxFilterOption("en", "English"), - CheckboxFilterOption("ar", "Arabic"), - CheckboxFilterOption("bg", "Bulgarian"), - CheckboxFilterOption("zh", "Chinese"), - CheckboxFilterOption("cs", "Czech"), - CheckboxFilterOption("da", "Danish"), - CheckboxFilterOption("nl", "Dutch"), - CheckboxFilterOption("fil", "Filipino"), - CheckboxFilterOption("fi", "Finnish"), - CheckboxFilterOption("fr", "French"), - CheckboxFilterOption("de", "German"), - CheckboxFilterOption("el", "Greek"), - CheckboxFilterOption("he", "Hebrew"), - CheckboxFilterOption("hi", "Hindi"), - CheckboxFilterOption("hu", "Hungarian"), - CheckboxFilterOption("id", "Indonesian"), - CheckboxFilterOption("it", "Italian"), - CheckboxFilterOption("ja", "Japanese"), - CheckboxFilterOption("ko", "Korean"), - CheckboxFilterOption("ms", "Malay"), - CheckboxFilterOption("pl", "Polish"), - CheckboxFilterOption("pt", "Portuguese"), - CheckboxFilterOption("pt_br", "Portuguese (Brazil)"), - CheckboxFilterOption("ro", "Romanian"), - CheckboxFilterOption("ru", "Russian"), - CheckboxFilterOption("es", "Spanish"), - CheckboxFilterOption("es_419", "Spanish (Latin America)"), - CheckboxFilterOption("sv", "Swedish"), - CheckboxFilterOption("th", "Thai"), - CheckboxFilterOption("tr", "Turkish"), - CheckboxFilterOption("uk", "Ukrainian"), - CheckboxFilterOption("vi", "Vietnamese"), - CheckboxFilterOption("af", "Afrikaans"), - CheckboxFilterOption("sq", "Albanian"), - CheckboxFilterOption("am", "Amharic"), - CheckboxFilterOption("hy", "Armenian"), - CheckboxFilterOption("az", "Azerbaijani"), - CheckboxFilterOption("be", "Belarusian"), - CheckboxFilterOption("bn", "Bengali"), - CheckboxFilterOption("bs", "Bosnian"), - CheckboxFilterOption("my", "Burmese"), - CheckboxFilterOption("km", "Cambodian"), - CheckboxFilterOption("ca", "Catalan"), - CheckboxFilterOption("ceb", "Cebuano"), - CheckboxFilterOption("zh_hk", "Chinese (Cantonese)"), - CheckboxFilterOption("zh_tw", "Chinese (Traditional)"), - CheckboxFilterOption("hr", "Croatian"), - CheckboxFilterOption("en_us", "English (United States)"), - CheckboxFilterOption("eo", "Esperanto"), - CheckboxFilterOption("et", "Estonian"), - CheckboxFilterOption("fo", "Faroese"), - CheckboxFilterOption("ka", "Georgian"), - CheckboxFilterOption("gn", "Guarani"), - CheckboxFilterOption("gu", "Gujarati"), - CheckboxFilterOption("ht", "Haitian Creole"), - CheckboxFilterOption("ha", "Hausa"), - CheckboxFilterOption("is", "Icelandic"), - CheckboxFilterOption("ig", "Igbo"), - CheckboxFilterOption("ga", "Irish"), - CheckboxFilterOption("jv", "Javanese"), - CheckboxFilterOption("kn", "Kannada"), - CheckboxFilterOption("kk", "Kazakh"), - CheckboxFilterOption("ku", "Kurdish"), - CheckboxFilterOption("ky", "Kyrgyz"), - CheckboxFilterOption("lo", "Laothian"), - CheckboxFilterOption("lv", "Latvian"), - CheckboxFilterOption("lt", "Lithuanian"), - CheckboxFilterOption("lb", "Luxembourgish"), - CheckboxFilterOption("mk", "Macedonian"), - CheckboxFilterOption("mg", "Malagasy"), - CheckboxFilterOption("ml", "Malayalam"), - CheckboxFilterOption("mt", "Maltese"), - CheckboxFilterOption("mi", "Maori"), - CheckboxFilterOption("mr", "Marathi"), - CheckboxFilterOption("mo", "Moldavian"), - CheckboxFilterOption("mn", "Mongolian"), - CheckboxFilterOption("ne", "Nepali"), - CheckboxFilterOption("no", "Norwegian"), - CheckboxFilterOption("ny", "Nyanja"), - CheckboxFilterOption("ps", "Pashto"), - CheckboxFilterOption("fa", "Persian"), - CheckboxFilterOption("rm", "Romansh"), - CheckboxFilterOption("sm", "Samoan"), - CheckboxFilterOption("sr", "Serbian"), - CheckboxFilterOption("sh", "Serbo-Croatian"), - CheckboxFilterOption("st", "Sesotho"), - CheckboxFilterOption("sn", "Shona"), - CheckboxFilterOption("sd", "Sindhi"), - CheckboxFilterOption("si", "Sinhalese"), - CheckboxFilterOption("sk", "Slovak"), - CheckboxFilterOption("sl", "Slovenian"), - CheckboxFilterOption("so", "Somali"), - CheckboxFilterOption("sw", "Swahili"), - CheckboxFilterOption("tg", "Tajik"), - CheckboxFilterOption("ta", "Tamil"), - CheckboxFilterOption("ti", "Tigrinya"), - CheckboxFilterOption("to", "Tonga"), - CheckboxFilterOption("tk", "Turkmen"), - CheckboxFilterOption("ur", "Urdu"), - CheckboxFilterOption("uz", "Uzbek"), - CheckboxFilterOption("yo", "Yoruba"), - CheckboxFilterOption("zu", "Zulu"), - CheckboxFilterOption("_t", "Other"), - // Lang options from bato.to brows not in publish.bato.to - CheckboxFilterOption("eu", "Basque"), - CheckboxFilterOption("pt-PT", "Portuguese (Portugal)"), - ).filterNot { it.value == siteLang } + private fun getGenreFilter() = + listOf( + TriStateFilterOption("artbook", "Artbook"), + TriStateFilterOption("cartoon", "Cartoon"), + TriStateFilterOption("comic", "Comic"), + TriStateFilterOption("doujinshi", "Doujinshi"), + TriStateFilterOption("imageset", "Imageset"), + TriStateFilterOption("manga", "Manga"), + TriStateFilterOption("manhua", "Manhua"), + TriStateFilterOption("manhwa", "Manhwa"), + TriStateFilterOption("webtoon", "Webtoon"), + TriStateFilterOption("western", "Western"), + TriStateFilterOption("shoujo", "Shoujo(G)"), + TriStateFilterOption("shounen", "Shounen(B)"), + TriStateFilterOption("josei", "Josei(W)"), + TriStateFilterOption("seinen", "Seinen(M)"), + TriStateFilterOption("yuri", "Yuri(GL)"), + TriStateFilterOption("yaoi", "Yaoi(BL)"), + TriStateFilterOption("futa", "Futa(WL)"), + TriStateFilterOption("bara", "Bara(ML)"), + TriStateFilterOption("gore", "Gore"), + TriStateFilterOption("bloody", "Bloody"), + TriStateFilterOption("violence", "Violence"), + TriStateFilterOption("ecchi", "Ecchi"), + TriStateFilterOption("adult", "Adult"), + TriStateFilterOption("mature", "Mature"), + TriStateFilterOption("smut", "Smut"), + TriStateFilterOption("hentai", "Hentai"), + TriStateFilterOption("_4_koma", "4-Koma"), + TriStateFilterOption("action", "Action"), + TriStateFilterOption("adaptation", "Adaptation"), + TriStateFilterOption("adventure", "Adventure"), + TriStateFilterOption("age_gap", "Age Gap"), + TriStateFilterOption("aliens", "Aliens"), + TriStateFilterOption("animals", "Animals"), + TriStateFilterOption("anthology", "Anthology"), + TriStateFilterOption("beasts", "Beasts"), + TriStateFilterOption("bodyswap", "Bodyswap"), + TriStateFilterOption("cars", "cars"), + TriStateFilterOption("cheating_infidelity", "Cheating/Infidelity"), + TriStateFilterOption("childhood_friends", "Childhood Friends"), + TriStateFilterOption("college_life", "College Life"), + TriStateFilterOption("comedy", "Comedy"), + TriStateFilterOption("contest_winning", "Contest Winning"), + TriStateFilterOption("cooking", "Cooking"), + TriStateFilterOption("crime", "crime"), + TriStateFilterOption("crossdressing", "Crossdressing"), + TriStateFilterOption("delinquents", "Delinquents"), + TriStateFilterOption("dementia", "Dementia"), + TriStateFilterOption("demons", "Demons"), + TriStateFilterOption("drama", "Drama"), + TriStateFilterOption("dungeons", "Dungeons"), + TriStateFilterOption("emperor_daughte", "Emperor's Daughter"), + TriStateFilterOption("fantasy", "Fantasy"), + TriStateFilterOption("fan_colored", "Fan-Colored"), + TriStateFilterOption("fetish", "Fetish"), + TriStateFilterOption("full_color", "Full Color"), + TriStateFilterOption("game", "Game"), + TriStateFilterOption("gender_bender", "Gender Bender"), + TriStateFilterOption("genderswap", "Genderswap"), + TriStateFilterOption("ghosts", "Ghosts"), + TriStateFilterOption("gyaru", "Gyaru"), + TriStateFilterOption("harem", "Harem"), + TriStateFilterOption("harlequin", "Harlequin"), + TriStateFilterOption("historical", "Historical"), + TriStateFilterOption("horror", "Horror"), + TriStateFilterOption("incest", "Incest"), + TriStateFilterOption("isekai", "Isekai"), + TriStateFilterOption("kids", "Kids"), + TriStateFilterOption("loli", "Loli"), + TriStateFilterOption("magic", "Magic"), + TriStateFilterOption("magical_girls", "Magical Girls"), + TriStateFilterOption("martial_arts", "Martial Arts"), + TriStateFilterOption("mecha", "Mecha"), + TriStateFilterOption("medical", "Medical"), + TriStateFilterOption("military", "Military"), + TriStateFilterOption("monster_girls", "Monster Girls"), + TriStateFilterOption("monsters", "Monsters"), + TriStateFilterOption("music", "Music"), + TriStateFilterOption("mystery", "Mystery"), + TriStateFilterOption("netorare", "Netorare/NTR"), + TriStateFilterOption("ninja", "Ninja"), + TriStateFilterOption("office_workers", "Office Workers"), + TriStateFilterOption("omegaverse", "Omegaverse"), + TriStateFilterOption("oneshot", "Oneshot"), + TriStateFilterOption("parody", "parody"), + TriStateFilterOption("philosophical", "Philosophical"), + TriStateFilterOption("police", "Police"), + TriStateFilterOption("post_apocalyptic", "Post-Apocalyptic"), + TriStateFilterOption("psychological", "Psychological"), + TriStateFilterOption("regression", "Regression"), + TriStateFilterOption("reincarnation", "Reincarnation"), + TriStateFilterOption("reverse_harem", "Reverse Harem"), + TriStateFilterOption("reverse_isekai", "Reverse Isekai"), + TriStateFilterOption("romance", "Romance"), + TriStateFilterOption("royal_family", "Royal Family"), + TriStateFilterOption("royalty", "Royalty"), + TriStateFilterOption("samurai", "Samurai"), + TriStateFilterOption("school_life", "School Life"), + TriStateFilterOption("sci_fi", "Sci-Fi"), + TriStateFilterOption("shota", "Shota"), + TriStateFilterOption("shoujo_ai", "Shoujo Ai"), + TriStateFilterOption("shounen_ai", "Shounen Ai"), + TriStateFilterOption("showbiz", "Showbiz"), + TriStateFilterOption("slice_of_life", "Slice of Life"), + TriStateFilterOption("sm_bdsm", "SM/BDSM/SUB-DOM"), + TriStateFilterOption("space", "Space"), + TriStateFilterOption("sports", "Sports"), + TriStateFilterOption("super_power", "Super Power"), + TriStateFilterOption("superhero", "Superhero"), + TriStateFilterOption("supernatural", "Supernatural"), + TriStateFilterOption("survival", "Survival"), + TriStateFilterOption("thriller", "Thriller"), + TriStateFilterOption("time_travel", "Time Travel"), + TriStateFilterOption("tower_climbing", "Tower Climbing"), + TriStateFilterOption("traditional_games", "Traditional Games"), + TriStateFilterOption("tragedy", "Tragedy"), + TriStateFilterOption("transmigration", "Transmigration"), + TriStateFilterOption("vampires", "Vampires"), + TriStateFilterOption("villainess", "Villainess"), + TriStateFilterOption("video_games", "Video Games"), + TriStateFilterOption("virtual_reality", "Virtual Reality"), + TriStateFilterOption("wuxia", "Wuxia"), + TriStateFilterOption("xianxia", "Xianxia"), + TriStateFilterOption("xuanhuan", "Xuanhuan"), + TriStateFilterOption("zombies", "Zombies"), + // Hidden Genres + TriStateFilterOption("shotacon", "shotacon"), + TriStateFilterOption("lolicon", "lolicon"), + TriStateFilterOption("award_winning", "Award Winning"), + TriStateFilterOption("youkai", "Youkai"), + TriStateFilterOption("uncategorized", "Uncategorized"), + ) + + private fun getLangFilter() = + listOf( + // Values exported from publish.bato.to + CheckboxFilterOption("en", "English"), + CheckboxFilterOption("ar", "Arabic"), + CheckboxFilterOption("bg", "Bulgarian"), + CheckboxFilterOption("zh", "Chinese"), + CheckboxFilterOption("cs", "Czech"), + CheckboxFilterOption("da", "Danish"), + CheckboxFilterOption("nl", "Dutch"), + CheckboxFilterOption("fil", "Filipino"), + CheckboxFilterOption("fi", "Finnish"), + CheckboxFilterOption("fr", "French"), + CheckboxFilterOption("de", "German"), + CheckboxFilterOption("el", "Greek"), + CheckboxFilterOption("he", "Hebrew"), + CheckboxFilterOption("hi", "Hindi"), + CheckboxFilterOption("hu", "Hungarian"), + CheckboxFilterOption("id", "Indonesian"), + CheckboxFilterOption("it", "Italian"), + CheckboxFilterOption("ja", "Japanese"), + CheckboxFilterOption("ko", "Korean"), + CheckboxFilterOption("ms", "Malay"), + CheckboxFilterOption("pl", "Polish"), + CheckboxFilterOption("pt", "Portuguese"), + CheckboxFilterOption("pt_br", "Portuguese (Brazil)"), + CheckboxFilterOption("ro", "Romanian"), + CheckboxFilterOption("ru", "Russian"), + CheckboxFilterOption("es", "Spanish"), + CheckboxFilterOption("es_419", "Spanish (Latin America)"), + CheckboxFilterOption("sv", "Swedish"), + CheckboxFilterOption("th", "Thai"), + CheckboxFilterOption("tr", "Turkish"), + CheckboxFilterOption("uk", "Ukrainian"), + CheckboxFilterOption("vi", "Vietnamese"), + CheckboxFilterOption("af", "Afrikaans"), + CheckboxFilterOption("sq", "Albanian"), + CheckboxFilterOption("am", "Amharic"), + CheckboxFilterOption("hy", "Armenian"), + CheckboxFilterOption("az", "Azerbaijani"), + CheckboxFilterOption("be", "Belarusian"), + CheckboxFilterOption("bn", "Bengali"), + CheckboxFilterOption("bs", "Bosnian"), + CheckboxFilterOption("my", "Burmese"), + CheckboxFilterOption("km", "Cambodian"), + CheckboxFilterOption("ca", "Catalan"), + CheckboxFilterOption("ceb", "Cebuano"), + CheckboxFilterOption("zh_hk", "Chinese (Cantonese)"), + CheckboxFilterOption("zh_tw", "Chinese (Traditional)"), + CheckboxFilterOption("hr", "Croatian"), + CheckboxFilterOption("en_us", "English (United States)"), + CheckboxFilterOption("eo", "Esperanto"), + CheckboxFilterOption("et", "Estonian"), + CheckboxFilterOption("fo", "Faroese"), + CheckboxFilterOption("ka", "Georgian"), + CheckboxFilterOption("gn", "Guarani"), + CheckboxFilterOption("gu", "Gujarati"), + CheckboxFilterOption("ht", "Haitian Creole"), + CheckboxFilterOption("ha", "Hausa"), + CheckboxFilterOption("is", "Icelandic"), + CheckboxFilterOption("ig", "Igbo"), + CheckboxFilterOption("ga", "Irish"), + CheckboxFilterOption("jv", "Javanese"), + CheckboxFilterOption("kn", "Kannada"), + CheckboxFilterOption("kk", "Kazakh"), + CheckboxFilterOption("ku", "Kurdish"), + CheckboxFilterOption("ky", "Kyrgyz"), + CheckboxFilterOption("lo", "Laothian"), + CheckboxFilterOption("lv", "Latvian"), + CheckboxFilterOption("lt", "Lithuanian"), + CheckboxFilterOption("lb", "Luxembourgish"), + CheckboxFilterOption("mk", "Macedonian"), + CheckboxFilterOption("mg", "Malagasy"), + CheckboxFilterOption("ml", "Malayalam"), + CheckboxFilterOption("mt", "Maltese"), + CheckboxFilterOption("mi", "Maori"), + CheckboxFilterOption("mr", "Marathi"), + CheckboxFilterOption("mo", "Moldavian"), + CheckboxFilterOption("mn", "Mongolian"), + CheckboxFilterOption("ne", "Nepali"), + CheckboxFilterOption("no", "Norwegian"), + CheckboxFilterOption("ny", "Nyanja"), + CheckboxFilterOption("ps", "Pashto"), + CheckboxFilterOption("fa", "Persian"), + CheckboxFilterOption("rm", "Romansh"), + CheckboxFilterOption("sm", "Samoan"), + CheckboxFilterOption("sr", "Serbian"), + CheckboxFilterOption("sh", "Serbo-Croatian"), + CheckboxFilterOption("st", "Sesotho"), + CheckboxFilterOption("sn", "Shona"), + CheckboxFilterOption("sd", "Sindhi"), + CheckboxFilterOption("si", "Sinhalese"), + CheckboxFilterOption("sk", "Slovak"), + CheckboxFilterOption("sl", "Slovenian"), + CheckboxFilterOption("so", "Somali"), + CheckboxFilterOption("sw", "Swahili"), + CheckboxFilterOption("tg", "Tajik"), + CheckboxFilterOption("ta", "Tamil"), + CheckboxFilterOption("ti", "Tigrinya"), + CheckboxFilterOption("to", "Tonga"), + CheckboxFilterOption("tk", "Turkmen"), + CheckboxFilterOption("ur", "Urdu"), + CheckboxFilterOption("uz", "Uzbek"), + CheckboxFilterOption("yo", "Yoruba"), + CheckboxFilterOption("zu", "Zulu"), + CheckboxFilterOption("_t", "Other"), + // Lang options from bato.to brows not in publish.bato.to + CheckboxFilterOption("eu", "Basque"), + CheckboxFilterOption("pt-PT", "Portuguese (Portugal)"), + ).filterNot { it.value == siteLang } companion object { private const val MIRROR_PREF_KEY = "MIRROR" private const val MIRROR_PREF_TITLE = "Mirror" private const val REMOVE_TITLE_VERSION_PREF = "REMOVE_TITLE_VERSION" - private val MIRROR_PREF_ENTRIES = arrayOf( - "zbato.org", - "batocomic.com", - "batocomic.net", - "batocomic.org", - "batotoo.com", - "batotwo.com", - "battwo.com", - "comiko.net", - "comiko.org", - "readtoto.com", - "readtoto.net", - "readtoto.org", - "dto.to", - "fto.to", - "jto.to", - "hto.to", - "mto.to", - "wto.to", - "xbato.com", - "xbato.net", - "xbato.org", - "zbato.com", - "zbato.net", - ) + private val MIRROR_PREF_ENTRIES = + arrayOf( + "zbato.org", + "batocomic.com", + "batocomic.net", + "batocomic.org", + "batotoo.com", + "batotwo.com", + "battwo.com", + "comiko.net", + "comiko.org", + "readtoto.com", + "readtoto.net", + "readtoto.org", + "dto.to", + "fto.to", + "jto.to", + "hto.to", + "mto.to", + "wto.to", + "xbato.com", + "xbato.net", + "xbato.org", + "zbato.com", + "zbato.net", + ) private val MIRROR_PREF_ENTRY_VALUES = MIRROR_PREF_ENTRIES.map { "https://$it" }.toTypedArray() private val MIRROR_PREF_DEFAULT_VALUE = MIRROR_PREF_ENTRY_VALUES[0] - private val DEPRECATED_MIRRORS = listOf( - "https://bato.to", - "https://mangatoto.com", - "https://mangatoto.net", - "https://mangatoto.org", - ) + private val DEPRECATED_MIRRORS = + listOf( + "https://bato.to", + "https://mangatoto.com", + "https://mangatoto.net", + "https://mangatoto.org", + ) private const val ALT_CHAPTER_LIST_PREF_KEY = "ALT_CHAPTER_LIST" private const val ALT_CHAPTER_LIST_PREF_TITLE = "Alternative Chapter List" diff --git a/src/pt/sussyscan/src/eu/kanade/tachiyomi/extension/pt/sussyscan/SussyToons.kt b/src/pt/sussyscan/src/eu/kanade/tachiyomi/extension/pt/sussyscan/SussyToons.kt index 2d48fe5ef65..0ac110295c0 100644 --- a/src/pt/sussyscan/src/eu/kanade/tachiyomi/extension/pt/sussyscan/SussyToons.kt +++ b/src/pt/sussyscan/src/eu/kanade/tachiyomi/extension/pt/sussyscan/SussyToons.kt @@ -31,20 +31,21 @@ import okhttp3.Interceptor import okhttp3.Protocol import okhttp3.Request import okhttp3.Response -import okhttp3.internal.http.HTTP_BAD_GATEWAY import org.jsoup.Jsoup import org.jsoup.select.Elements import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import java.io.IOException +import java.net.HttpURLConnection.HTTP_BAD_GATEWAY import java.text.SimpleDateFormat import java.util.Locale import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit -class SussyToons : HttpSource(), ConfigurableSource { - +class SussyToons : + HttpSource(), + ConfigurableSource { override val name = "Sussy Toons" override val lang = "pt-BR" @@ -75,26 +76,30 @@ class SussyToons : HttpSource(), ConfigurableSource { get() = preferences.getString(PAGE_SCRIPT_URL_PREF, "")!! set(value) = preferences.edit().putString(PAGE_SCRIPT_URL_PREF, value).apply() - override val baseUrl: String get() = when { - isCi -> defaultBaseUrl - else -> preferences.getString(BASE_URL_PREF, defaultBaseUrl)!! - } + override val baseUrl: String get() = + when { + isCi -> defaultBaseUrl + else -> preferences.getString(BASE_URL_PREF, defaultBaseUrl)!! + } private val defaultBaseUrl: String = "https://www.sussytoons.site" private val defaultApiUrl: String = "https://api-dev.sussytoons.site" - override val client = network.cloudflareClient.newBuilder() - .rateLimit(2) - .addInterceptor(::findApiUrl) - .addInterceptor(::findChapterUrl) - .addInterceptor(::chapterPages) - .addInterceptor(::imageLocation) - .build() + override val client = + network.cloudflareClient + .newBuilder() + .rateLimit(2) + .addInterceptor(::findApiUrl) + .addInterceptor(::findChapterUrl) + .addInterceptor(::chapterPages) + .addInterceptor(::imageLocation) + .build() init { preferences.getString(DEFAULT_BASE_URL_PREF, null).let { domain -> if (domain != defaultBaseUrl) { - preferences.edit() + preferences + .edit() .putString(BASE_URL_PREF, defaultBaseUrl) .putString(DEFAULT_BASE_URL_PREF, defaultBaseUrl) .apply() @@ -102,7 +107,8 @@ class SussyToons : HttpSource(), ConfigurableSource { } preferences.getString(API_DEFAULT_BASE_URL_PREF, null).let { domain -> if (domain != defaultApiUrl) { - preferences.edit() + preferences + .edit() .putString(API_BASE_URL_PREF, defaultApiUrl) .putString(API_DEFAULT_BASE_URL_PREF, defaultApiUrl) .apply() @@ -110,14 +116,14 @@ class SussyToons : HttpSource(), ConfigurableSource { } } - override fun headersBuilder() = super.headersBuilder() - .set("scan-id", "1") // Required header for requests + override fun headersBuilder() = + super + .headersBuilder() + .set("scan-id", "1") // Required header for requests // ============================= Popular ================================== - override fun popularMangaRequest(page: Int): Request { - return GET("$apiUrl/obras/top5", headers) - } + override fun popularMangaRequest(page: Int): Request = GET("$apiUrl/obras/top5", headers) override fun popularMangaParse(response: Response): MangasPage { val dto = response.parseAs>>() @@ -128,10 +134,13 @@ class SussyToons : HttpSource(), ConfigurableSource { // ============================= Latest =================================== override fun latestUpdatesRequest(page: Int): Request { - val url = "$apiUrl/obras/novos-capitulos".toHttpUrl().newBuilder() - .addQueryParameter("pagina", page.toString()) - .addQueryParameter("limite", "24") - .build() + val url = + "$apiUrl/obras/novos-capitulos" + .toHttpUrl() + .newBuilder() + .addQueryParameter("pagina", page.toString()) + .addQueryParameter("limite", "24") + .build() return GET(url, headers) } @@ -143,12 +152,19 @@ class SussyToons : HttpSource(), ConfigurableSource { // ============================= Search =================================== - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = "$apiUrl/obras".toHttpUrl().newBuilder() - .addQueryParameter("pagina", page.toString()) - .addQueryParameter("limite", "8") - .addQueryParameter("obr_nome", query) - .build() + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { + val url = + "$apiUrl/obras" + .toHttpUrl() + .newBuilder() + .addQueryParameter("pagina", page.toString()) + .addQueryParameter("limite", "8") + .addQueryParameter("obr_nome", query) + .build() return GET(url, headers) } @@ -159,20 +175,25 @@ class SussyToons : HttpSource(), ConfigurableSource { override fun getMangaUrl(manga: SManga) = "$baseUrl${manga.url}" override fun mangaDetailsRequest(manga: SManga): Request { - val url = "$apiUrl/obras".toHttpUrl().newBuilder() - .addPathSegment(manga.id) - .fragment("$mangaPagePrefix${getMangaUrl(manga)}") - .build() + val url = + "$apiUrl/obras" + .toHttpUrl() + .newBuilder() + .addPathSegment(manga.id) + .fragment("$MANGA_PAGE_PREFIX${getMangaUrl(manga)}") + .build() return GET(url, headers) } - override fun mangaDetailsParse(response: Response) = - response.parseAs>().results.toSManga() + override fun mangaDetailsParse(response: Response) = response.parseAs>().results.toSManga() private val SManga.id: String get() { - val mangaUrl = apiUrl.toHttpUrl().newBuilder() - .addPathSegments(url) - .build() + val mangaUrl = + apiUrl + .toHttpUrl() + .newBuilder() + .addPathSegments(url) + .build() return mangaUrl.pathSegments[2] } @@ -180,71 +201,87 @@ class SussyToons : HttpSource(), ConfigurableSource { override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga) - override fun chapterListParse(response: Response): List { - return response.parseAs>().results.chapters.map { - SChapter.create().apply { - name = it.name - it.chapterNumber?.let { - chapter_number = it + override fun chapterListParse(response: Response): List = + response + .parseAs>() + .results.chapters + .map { + SChapter.create().apply { + name = it.name + it.chapterNumber?.let { + chapter_number = it + } + val chapterApiUrl = + apiUrl + .toHttpUrl() + .newBuilder() + .addEncodedPathSegments(chapterUrl!!) + .addPathSegment(it.id.toString()) + .build() + setUrlWithoutDomain(chapterApiUrl.toString()) + date_upload = it.updateAt.toDate() } - val chapterApiUrl = apiUrl.toHttpUrl().newBuilder() - .addEncodedPathSegments(chapterUrl!!) - .addPathSegment(it.id.toString()) - .build() - setUrlWithoutDomain(chapterApiUrl.toString()) - date_upload = it.updateAt.toDate() - } - }.sortedBy(SChapter::chapter_number).reversed() - } + }.sortedBy(SChapter::chapter_number) + .reversed() // ============================= Pages ==================================== - override fun pageListRequest(chapter: SChapter): Request { - return super.pageListRequest(chapter).let { request -> - val url = request.url.newBuilder() - .fragment("$pageImagePrefix${chapter.url}") - .build() + override fun pageListRequest(chapter: SChapter): Request = + super.pageListRequest(chapter).let { request -> + val url = + request.url + .newBuilder() + .fragment("$PAGE_IMAGE_PREFIX${chapter.url}") + .build() - request.newBuilder() + request + .newBuilder() .url(url) .build() } - } private var pageUrlSegment: String? = null override fun pageListParse(response: Response): List { pageUrlSegment = pageUrlSegment ?: findPageUrlSegment(response) - val chapterPageId = response.request.url.pathSegments.last() + val chapterPageId = + response.request.url.pathSegments + .last() - val chapterUrl = response.request.url.fragment - ?.substringAfter(pageImagePrefix) - ?: throw Exception("Não foi possivel carregar as páginas") + val chapterUrl = + response.request.url.fragment + ?.substringAfter(PAGE_IMAGE_PREFIX) + ?: throw Exception("Não foi possivel carregar as páginas") - val url = apiUrl.toHttpUrl().newBuilder() - .addEncodedPathSegments(pageUrlSegment!!) - .addPathSegment(chapterPageId) - .fragment( - "$chapterPagePrefix${"$baseUrl$chapterUrl"}", - ) - .build() + val url = + apiUrl + .toHttpUrl() + .newBuilder() + .addEncodedPathSegments(pageUrlSegment!!) + .addPathSegment(chapterPageId) + .fragment( + "$CHAPTER_PAGE_PREFIX${"$baseUrl$chapterUrl"}", + ).build() val res = client.newCall(GET(url, headers)).execute() val dto = res.parseAs>().results return dto.pages.mapIndexed { index, image -> - val imageUrl = when { - image.isWordPressContent() -> { - CDN_URL.toHttpUrl().newBuilder() - .addPathSegments("wp-content/uploads/WP-manga/data") - .addPathSegments(image.src.toPathSegment()) - .build() - } - else -> { - "$CDN_URL/scans/${dto.manga.scanId}/obras/${dto.manga.id}/capitulos/${dto.chapterNumber}/${image.src}" - .toHttpUrl() + val imageUrl = + when { + image.isWordPressContent() -> { + CDN_URL + .toHttpUrl() + .newBuilder() + .addPathSegments("wp-content/uploads/WP-manga/data") + .addPathSegments(image.src.toPathSegment()) + .build() + } + else -> { + "$CDN_URL/scans/${dto.manga.scanId}/obras/${dto.manga.id}/capitulos/${dto.chapterNumber}/${image.src}" + .toHttpUrl() + } } - } Page(index, imageUrl = imageUrl.toString()) } } @@ -253,30 +290,39 @@ class SussyToons : HttpSource(), ConfigurableSource { * Get the “dynamic” path segment of the chapter page */ private fun findPageUrlSegment(response: Response): String { - val scriptUrls = when { - pageScriptUrl.isNotBlank() -> listOf(pageScriptUrl to headers) - else -> emptyList() - } + val scriptUrls = + when { + pageScriptUrl.isNotBlank() -> listOf(pageScriptUrl to headers) + else -> emptyList() + } - val script = loadJsScript( - urls = scriptUrls, - doRequest = { client.newCall(it).execute() }, - pattern = pageUrlRegex, - fallback = { fetchAllNextJsScriptUrls(response.request) }, - ) + val script = + loadJsScript( + urls = scriptUrls, + doRequest = { client.newCall(it).execute() }, + pattern = pageUrlRegex, + fallback = { fetchAllNextJsScriptUrls(response.request) }, + ) pageScriptUrl = script.url - return pageUrlRegex.find(script.body)?.groups?.get(2)?.value?.toPathSegment() + return pageUrlRegex + .find(script.body) + ?.groups + ?.get(2) + ?.value + ?.toPathSegment() ?: throw IOException("Não foi encontrar o caminho das páginas") } override fun imageUrlParse(response: Response): String = "" override fun imageUrlRequest(page: Page): Request { - val imageHeaders = headers.newBuilder() - .add("Referer", "$baseUrl/") - .build() + val imageHeaders = + headers + .newBuilder() + .add("Referer", "$baseUrl/") + .build() return GET(page.url, imageHeaders) } @@ -288,26 +334,36 @@ class SussyToons : HttpSource(), ConfigurableSource { private fun findApiUrl(chain: Interceptor.Chain): Response { val request = chain.request() - val response: Response = try { - chain.proceed(request) - } catch (ex: Exception) { - chain.createBadGatewayResponse(request) - } + val response: Response = + try { + chain.proceed(request) + } catch (ex: Exception) { + chain.createBadGatewayResponse(request) + } - if (response.isSuccessful || request.url.toString().contains(apiUrl).not()) { + if (response.isSuccessful || + request.url + .toString() + .contains(apiUrl) + .not() + ) { return response } response.close() fetchApiUrl(chain).forEach { urlCandidate -> - val url = request.url.toString() - .replace(apiUrl, urlCandidate) - .toHttpUrl() - - val newRequest = request.newBuilder() - .url(url) - .build() + val url = + request.url + .toString() + .replace(apiUrl, urlCandidate) + .toHttpUrl() + + val newRequest = + request + .newBuilder() + .url(url) + .build() val localResponse = chain.proceed(newRequest) if (localResponse.isSuccessful.not()) { @@ -326,29 +382,37 @@ class SussyToons : HttpSource(), ConfigurableSource { ) } - private fun Interceptor.Chain.createBadGatewayResponse(request: Request): Response { - return Response.Builder() + private fun Interceptor.Chain.createBadGatewayResponse(request: Request): Response = + Response + .Builder() .request(request) .protocol(Protocol.HTTP_1_1) .message("") .code(HTTP_BAD_GATEWAY) .build() - } private fun fetchApiUrl(chain: Interceptor.Chain): List { - val scripts = chain.proceed(GET(baseUrl, headers)).asJsoup() - .select("script[src*=next]:not([nomodule]):not([src*=app])") - - val script = getScriptBodyWithUrls(scripts, chain) - ?: throw Exception("Não foi possivel localizar a URL da API") - - return apiUrlRegex.findAll(script) + val scripts = + chain + .proceed(GET(baseUrl, headers)) + .asJsoup() + .select("script[src*=next]:not([nomodule]):not([src*=app])") + + val script = + getScriptBodyWithUrls(scripts, chain) + ?: throw Exception("Não foi possivel localizar a URL da API") + + return apiUrlRegex + .findAll(script) .flatMap { stringsRegex.findAll(it.value).map { match -> match.groupValues[1] } } .filter(urlRegex::containsMatchIn) .toList() } - private fun getScriptBodyWithUrls(scripts: Elements, chain: Interceptor.Chain): String? { + private fun getScriptBodyWithUrls( + scripts: Elements, + chain: Interceptor.Chain, + ): String? { val elements = scripts.toList().reversed() for (element in elements) { val scriptUrl = element.absUrl("src") @@ -366,27 +430,35 @@ class SussyToons : HttpSource(), ConfigurableSource { private fun findChapterUrl(chain: Interceptor.Chain): Response { val request = chain.request() - val mangaUrl = request.url.fragment - ?.takeIf { - it.contains(mangaPagePrefix, ignoreCase = true) && chapterUrl.isNullOrBlank() - }?.substringAfter(mangaPagePrefix) - ?: return chain.proceed(request) - - val scriptUrls = when { - chapterScriptUrl.isNotBlank() -> listOf(chapterScriptUrl to headers) - else -> emptyList() - } + val mangaUrl = + request.url.fragment + ?.takeIf { + it.contains(MANGA_PAGE_PREFIX, ignoreCase = true) && chapterUrl.isNullOrBlank() + }?.substringAfter(MANGA_PAGE_PREFIX) + ?: return chain.proceed(request) + + val scriptUrls = + when { + chapterScriptUrl.isNotBlank() -> listOf(chapterScriptUrl to headers) + else -> emptyList() + } - val script = loadJsScript( - urls = scriptUrls, - doRequest = chain::proceed, - pattern = chapterUrlRegex, - fallback = { fetchAllNextJsScriptUrls(GET(mangaUrl, headers)) }, - ) + val script = + loadJsScript( + urls = scriptUrls, + doRequest = chain::proceed, + pattern = chapterUrlRegex, + fallback = { fetchAllNextJsScriptUrls(GET(mangaUrl, headers)) }, + ) chapterScriptUrl = script.url - chapterUrl = chapterUrlRegex.find(script.body)?.groups?.get(1)?.value?.toPathSegment() + chapterUrl = chapterUrlRegex + .find(script.body) + ?.groups + ?.get(1) + ?.value + ?.toPathSegment() ?: throw IOException("Não foi possivel extrair a URL do capitulo") return chain.proceed(request) @@ -398,13 +470,15 @@ class SussyToons : HttpSource(), ConfigurableSource { pattern: Regex, fallback: (() -> List>)? = null, ): Script { - val script = urls.map { pair -> - val request = GET(pair.first, pair.second) - Script( - url = request.url.toString(), - body = doRequest(request).use { response -> response.body.string() }, - ) - }.firstOrNull { pattern.containsMatchIn(it.body) } + val script = + urls + .map { pair -> + val request = GET(pair.first, pair.second) + Script( + url = request.url.toString(), + body = doRequest(request).use { response -> response.body.string() }, + ) + }.firstOrNull { pattern.containsMatchIn(it.body) } return script ?: fallback?.let { urlList -> loadJsScript(urlList(), doRequest = doRequest, pattern = pattern) @@ -422,9 +496,11 @@ class SussyToons : HttpSource(), ConfigurableSource { if (url.contains(CDN_URL, ignoreCase = true)) { response.close() - val newRequest = request.newBuilder() - .url(url.replace(CDN_URL, OLDI_URL, ignoreCase = true)) - .build() + val newRequest = + request + .newBuilder() + .url(url.replace(CDN_URL, OLDI_URL, ignoreCase = true)) + .build() return chain.proceed(newRequest) } @@ -436,18 +512,26 @@ class SussyToons : HttpSource(), ConfigurableSource { */ private fun chapterPages(chain: Interceptor.Chain): Response { val request = chain.request() - val chapterUrl = request.url.fragment - ?.takeIf { it.contains(chapterPagePrefix) } - ?.substringAfter(chapterPagePrefix)?.toHttpUrl()?.newBuilder()?.fragment(null) - ?.build() - ?: return chain.proceed(request) - - val originUrl = request.url.newBuilder() - .fragment(null) - .build() + val chapterUrl = + request.url.fragment + ?.takeIf { it.contains(CHAPTER_PAGE_PREFIX) } + ?.substringAfter(CHAPTER_PAGE_PREFIX) + ?.toHttpUrl() + ?.newBuilder() + ?.fragment(null) + ?.build() + ?: return chain.proceed(request) + + val originUrl = + request.url + .newBuilder() + .fragment(null) + .build() - val newRequest = request.newBuilder() - .url(originUrl) + val newRequest = + request + .newBuilder() + .url(originUrl) chapterPageHeaders?.let { headers -> newRequest.headers(headers) @@ -458,43 +542,50 @@ class SussyToons : HttpSource(), ConfigurableSource { response.close() } - val chapterPageRequest = request.newBuilder() - .url(chapterUrl) - .build() + val chapterPageRequest = + request + .newBuilder() + .url(chapterUrl) + .build() return chain.proceed(fetchChapterPagesHeaders(chapterPageRequest, newRequest.build())) } @SuppressLint("SetJavaScriptEnabled") - private fun fetchChapterPagesHeaders(baseRequest: Request, originRequest: Request): Request { - fun WebResourceRequest.isOriginRequest() = - originRequest.url.toString().equals(this.url.toString(), ignoreCase = true) - - chapterPageHeaders = handlingWithWebResourceRequest( - baseRequest, - initial = headersBuilder(), - stopCondition = { _, _, resource -> - resource.isOriginRequest() && resource.method.equals("GET", true) - }, - fold = { headers, _, resource -> - headers.apply { - if (resource.isOriginRequest().not() || resource.method.equals("GET", true).not()) { - return@apply + private fun fetchChapterPagesHeaders( + baseRequest: Request, + originRequest: Request, + ): Request { + fun WebResourceRequest.isOriginRequest() = originRequest.url.toString().equals(this.url.toString(), ignoreCase = true) + + chapterPageHeaders = + handlingWithWebResourceRequest( + baseRequest, + initial = headersBuilder(), + stopCondition = { _, _, resource -> + resource.isOriginRequest() && resource.method.equals("GET", true) + }, + fold = { headers, _, resource -> + headers.apply { + if (resource.isOriginRequest().not() || resource.method.equals("GET", true).not()) { + return@apply + } + fill(resource.requestHeaders) } - fill(resource.requestHeaders) - } - }, - ).build() + }, + ).build() - return originRequest.newBuilder() + return originRequest + .newBuilder() .headers(chapterPageHeaders!!) .build() } @SuppressLint("SetJavaScriptEnabled") private fun fetchAllNextJsScriptUrls(baseRequest: Request): List> { - fun WebResourceRequest.isNextJSUrl() = this.url.toString().contains("_next", ignoreCase = true) && - this.url.toString().contains(".js", ignoreCase = true) + fun WebResourceRequest.isNextJSUrl() = + this.url.toString().contains("_next", ignoreCase = true) && + this.url.toString().contains(".js", ignoreCase = true) return handlingWithWebResourceRequest( baseRequest, @@ -508,9 +599,10 @@ class SussyToons : HttpSource(), ConfigurableSource { if (resource.isNextJSUrl().not()) { return@apply } - val headers = base.headers.newBuilder().apply { - fill(resource.requestHeaders) - } + val headers = + base.headers.newBuilder().apply { + fill(resource.requestHeaders) + } add(resource.url.toString() to headers.build()) } }, @@ -539,18 +631,19 @@ class SussyToons : HttpSource(), ConfigurableSource { cacheMode = WebSettings.LOAD_DEFAULT } } - webView?.webViewClient = object : WebViewClient() { - override fun shouldInterceptRequest( - view: WebView?, - request: WebResourceRequest, - ): WebResourceResponse? { - state = fold(state, baseRequest, request) - if (stopCondition(state, baseRequest, request)) { - latch.countDown() + webView?.webViewClient = + object : WebViewClient() { + override fun shouldInterceptRequest( + view: WebView?, + request: WebResourceRequest, + ): WebResourceResponse? { + state = fold(state, baseRequest, request) + if (stopCondition(state, baseRequest, request)) { + latch.countDown() + } + return super.shouldInterceptRequest(view, request) } - return super.shouldInterceptRequest(view, request) } - } webView?.loadUrl(baseRequest.url.toString(), baseRequest.headers.toMap()) } @@ -565,50 +658,51 @@ class SussyToons : HttpSource(), ConfigurableSource { return state } - private fun Headers.Builder.fill(from: Map): Headers.Builder { - return from.entries.fold(this) { builder, entry -> + private fun Headers.Builder.fill(from: Map): Headers.Builder = + from.entries.fold(this) { builder, entry -> builder.set(entry.key, entry.value) } - } // ============================= Settings ==================================== override fun setupPreferenceScreen(screen: PreferenceScreen) { - val fields = listOf( - EditTextPreference(screen.context).apply { - key = BASE_URL_PREF - title = BASE_URL_PREF_TITLE - summary = URL_PREF_SUMMARY - - dialogTitle = BASE_URL_PREF_TITLE - dialogMessage = "URL padrão:\n$defaultBaseUrl" - - setDefaultValue(defaultBaseUrl) - setOnPreferenceChangeListener { _, _ -> - Toast.makeText(screen.context, RESTART_APP_MESSAGE, Toast.LENGTH_LONG).show() - true - } - }, - EditTextPreference(screen.context).apply { - key = API_BASE_URL_PREF - title = API_BASE_URL_PREF_TITLE - summary = buildString { - append("Se não souber como verificar a URL da API, ") - append("busque suporte no Discord do repositório de extensões.") - appendLine(URL_PREF_SUMMARY) - append("\n⚠ A fonte não oferece suporte para essa extensão.") - } - - dialogTitle = BASE_URL_PREF_TITLE - dialogMessage = "URL da API padrão:\n$defaultApiUrl" - - setDefaultValue(defaultApiUrl) - setOnPreferenceChangeListener { _, _ -> - Toast.makeText(screen.context, RESTART_APP_MESSAGE, Toast.LENGTH_LONG).show() - true - } - }, - ) + val fields = + listOf( + EditTextPreference(screen.context).apply { + key = BASE_URL_PREF + title = BASE_URL_PREF_TITLE + summary = URL_PREF_SUMMARY + + dialogTitle = BASE_URL_PREF_TITLE + dialogMessage = "URL padrão:\n$defaultBaseUrl" + + setDefaultValue(defaultBaseUrl) + setOnPreferenceChangeListener { _, _ -> + Toast.makeText(screen.context, RESTART_APP_MESSAGE, Toast.LENGTH_LONG).show() + true + } + }, + EditTextPreference(screen.context).apply { + key = API_BASE_URL_PREF + title = API_BASE_URL_PREF_TITLE + summary = + buildString { + append("Se não souber como verificar a URL da API, ") + append("busque suporte no Discord do repositório de extensões.") + appendLine(URL_PREF_SUMMARY) + append("\n⚠ A fonte não oferece suporte para essa extensão.") + } + + dialogTitle = BASE_URL_PREF_TITLE + dialogMessage = "URL da API padrão:\n$defaultApiUrl" + + setDefaultValue(defaultApiUrl) + setOnPreferenceChangeListener { _, _ -> + Toast.makeText(screen.context, RESTART_APP_MESSAGE, Toast.LENGTH_LONG).show() + true + } + }, + ) fields.forEach(screen::addPreference) } @@ -621,16 +715,20 @@ class SussyToons : HttpSource(), ConfigurableSource { ) private fun MangaDto.toSManga(): SManga { - val sManga = SManga.create().apply { - title = name - thumbnail_url = thumbnail - initialized = true - val mangaUrl = "$baseUrl/obra".toHttpUrl().newBuilder() - .addPathSegment(this@toSManga.id.toString()) - .addPathSegment(this@toSManga.slug!!) - .build() - setUrlWithoutDomain(mangaUrl.toString()) - } + val sManga = + SManga.create().apply { + title = name + thumbnail_url = thumbnail + initialized = true + val mangaUrl = + "$baseUrl/obra" + .toHttpUrl() + .newBuilder() + .addPathSegment(this@toSManga.id.toString()) + .addPathSegment(this@toSManga.slug!!) + .build() + setUrlWithoutDomain(mangaUrl.toString()) + } description?.let { Jsoup.parseBodyFragment(it).let { sManga.description = it.text() } } sManga.status = status.toStatus() @@ -638,28 +736,36 @@ class SussyToons : HttpSource(), ConfigurableSource { return sManga } - private inline fun Response.parseAs(): T = use { - return json.decodeFromStream(body.byteStream()) - } + private inline fun Response.parseAs(): T = + use { + return json.decodeFromStream(body.byteStream()) + } private fun String.toDate() = - try { dateFormat.parse(this)!!.time } catch (_: Exception) { 0L } + try { + dateFormat.parse(this)!!.time + } catch (_: Exception) { + 0L + } /** * Normalizes path segments: * Ex: [ "/a/b/", "/a/b", "a/b/", "a/b" ] * Result: "a/b" */ - private fun String.toPathSegment() = this.trim().split("/") - .filter(String::isNotEmpty) - .joinToString("/") + private fun String.toPathSegment() = + this + .trim() + .split("/") + .filter(String::isNotEmpty) + .joinToString("/") companion object { const val CDN_URL = "https://cdn.sussytoons.site" const val OLDI_URL = "https://oldi.sussytoons.site" - const val mangaPagePrefix = "mangaPage:" - const val chapterPagePrefix = "chapterPage:" - const val pageImagePrefix = "pageImage:" + const val MANGA_PAGE_PREFIX = "mangaPage:" + const val CHAPTER_PAGE_PREFIX = "chapterPage:" + const val PAGE_IMAGE_PREFIX = "pageImage:" private const val URL_PREF_SUMMARY = "Para uso temporário, se a extensão for atualizada, a alteração será perdida." diff --git a/src/pt/sussyscan/src/eu/kanade/tachiyomi/extension/pt/sussyscan/SussyToonsDto.kt b/src/pt/sussyscan/src/eu/kanade/tachiyomi/extension/pt/sussyscan/SussyToonsDto.kt index 0168813333d..aa9cd12dc77 100644 --- a/src/pt/sussyscan/src/eu/kanade/tachiyomi/extension/pt/sussyscan/SussyToonsDto.kt +++ b/src/pt/sussyscan/src/eu/kanade/tachiyomi/extension/pt/sussyscan/SussyToonsDto.kt @@ -39,14 +39,13 @@ class MangaDto( @SerialName("stt_nome") val value: String?, ) { - fun toStatus(): Int { - return when (value?.lowercase()) { + fun toStatus(): Int = + when (value?.lowercase()) { "em andamento" -> SManga.ONGOING "completo" -> SManga.COMPLETED "hiato" -> SManga.ON_HIATUS else -> SManga.UNKNOWN } - } } } diff --git a/src/vi/doctruyen3q/src/eu/kanade/tachiyomi/extension/vi/doctruyen3q/DocTruyen3Q.kt b/src/vi/doctruyen3q/src/eu/kanade/tachiyomi/extension/vi/doctruyen3q/DocTruyen3Q.kt index 568fab6d302..3de111cecfb 100644 --- a/src/vi/doctruyen3q/src/eu/kanade/tachiyomi/extension/vi/doctruyen3q/DocTruyen3Q.kt +++ b/src/vi/doctruyen3q/src/eu/kanade/tachiyomi/extension/vi/doctruyen3q/DocTruyen3Q.kt @@ -15,41 +15,51 @@ import java.text.SimpleDateFormat import java.util.Locale import java.util.TimeZone -class DocTruyen3Q : WPComics( - "DocTruyen3Q", - "https://doctruyen3qui.com", - "vi", - dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ROOT).apply { - timeZone = TimeZone.getTimeZone("Asia/Ho_Chi_Minh") - }, - gmtOffset = null, -) { - override val client = super.client.newBuilder() - .rateLimit(3) - .build() +class DocTruyen3Q : + WPComics( + "DocTruyen3Q", + "https://doctruyen3qui.com", + "vi", + dateFormat = + SimpleDateFormat("dd-MM-yyyy", Locale.ROOT).apply { + timeZone = TimeZone.getTimeZone("Asia/Ho_Chi_Minh") + }, + gmtOffset = null, + ) { + override val client = + super.client + .newBuilder() + .rateLimit(3) + .build() - override fun pageListParse(document: Document): List { - return document.select(".page-chapter a img, .page-chapter img").mapIndexed { index, element -> - val img = element.attr("abs:src").takeIf { it.isNotBlank() } ?: element.attr("abs:data-original") - Page(index, imageUrl = img) - }.distinctBy { it.imageUrl } - } + override fun pageListParse(document: Document): List = + document + .select(".page-chapter a img, .page-chapter img") + .mapIndexed { index, element -> + val img = element.attr("abs:src").takeIf { it.isNotBlank() } ?: element.attr("abs:data-original") + Page(index, imageUrl = img) + }.distinctBy { it.imageUrl } override fun popularMangaSelector() = "div.item-manga div.item" - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - element.selectFirst("h3 a")?.let { - title = it.text() - setUrlWithoutDomain(it.attr("abs:href")) + override fun popularMangaFromElement(element: Element) = + SManga.create().apply { + element.selectFirst("h3 a")?.let { + title = it.text() + setUrlWithoutDomain(it.attr("abs:href")) + } + thumbnail_url = imageOrNull(element.selectFirst("img")!!) } - thumbnail_url = imageOrNull(element.selectFirst("img")!!) - } override fun searchMangaSelector() = popularMangaSelector() override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { val url = "$baseUrl/$searchPath".toHttpUrl().newBuilder() filters.forEach { filter -> @@ -68,21 +78,21 @@ class DocTruyen3Q : WPComics( return GET(url.toString(), headers) } - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - title = document.selectFirst("h1.title-manga")!!.text() - description = document.selectFirst("p.detail-summary")?.text() - status = document.selectFirst("li.status p.detail-info span")?.text().toStatus() - genre = document.select("li.category p.detail-info a")?.joinToString { it.text() } - thumbnail_url = imageOrNull(document.selectFirst("img.image-comic")!!) - } + override fun mangaDetailsParse(document: Document) = + SManga.create().apply { + title = document.selectFirst("h1.title-manga")!!.text() + description = document.selectFirst("p.detail-summary")?.text() + status = document.selectFirst("li.status p.detail-info span")?.text().toStatus() + genre = document.select("li.category p.detail-info a")?.joinToString { it.text() } + thumbnail_url = imageOrNull(document.selectFirst("img.image-comic")!!) + } override fun chapterListSelector() = "div.list-chapter li.row:not(.heading):not([style])" - override fun chapterFromElement(element: Element): SChapter { - return super.chapterFromElement(element).apply { + override fun chapterFromElement(element: Element): SChapter = + super.chapterFromElement(element).apply { date_upload = element.selectFirst(".chapters + div")?.text().toDate() } - } override val genresSelector = ".categories-detail ul.nav li:not(.active) a" } diff --git a/src/vi/toptruyen/src/eu/kanade/tachiyomi/extension/vi/toptruyen/TopTruyen.kt b/src/vi/toptruyen/src/eu/kanade/tachiyomi/extension/vi/toptruyen/TopTruyen.kt index 49c1fbc2d85..8d76a2b0bea 100644 --- a/src/vi/toptruyen/src/eu/kanade/tachiyomi/extension/vi/toptruyen/TopTruyen.kt +++ b/src/vi/toptruyen/src/eu/kanade/tachiyomi/extension/vi/toptruyen/TopTruyen.kt @@ -15,41 +15,50 @@ import java.text.SimpleDateFormat import java.util.Locale import java.util.TimeZone -class TopTruyen : WPComics( - "Top Truyen", - "https://www.toptruyentv.net", - "vi", - dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ROOT).apply { - timeZone = TimeZone.getTimeZone("Asia/Ho_Chi_Minh") - }, - gmtOffset = null, -) { - override val client = super.client.newBuilder() - .rateLimit(3) - .build() +class TopTruyen : + WPComics( + "Top Truyen", + "https://www.toptruyentv.net", + "vi", + dateFormat = + SimpleDateFormat("dd-MM-yyyy", Locale.ROOT).apply { + timeZone = TimeZone.getTimeZone("Asia/Ho_Chi_Minh") + }, + gmtOffset = null, + ) { + override val client = + super.client + .newBuilder() + .rateLimit(3) + .build() - override fun pageListParse(document: Document): List { - return document.select(".page-chapter img") + override fun pageListParse(document: Document): List = + document + .select(".page-chapter img") .mapNotNull(::imageOrNull) .distinct() .mapIndexed { i, image -> Page(i, imageUrl = image) } - } override fun popularMangaSelector() = "div.item-manga div.item" - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - element.select("h3 a").let { - title = it.text() - setUrlWithoutDomain(it.attr("abs:href")) + override fun popularMangaFromElement(element: Element) = + SManga.create().apply { + element.select("h3 a").let { + title = it.text() + setUrlWithoutDomain(it.attr("abs:href")) + } + thumbnail_url = imageOrNull(element.selectFirst("img")!!) } - thumbnail_url = imageOrNull(element.selectFirst("img")!!) - } override fun searchMangaSelector() = popularMangaSelector() override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { val url = "$baseUrl/$searchPath".toHttpUrl().newBuilder() filters.forEach { filter -> @@ -68,21 +77,21 @@ class TopTruyen : WPComics( return GET(url.toString(), headers) } - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - title = document.selectFirst("h1.title-manga")!!.text() - description = document.selectFirst("p.detail-summary")?.text() - status = document.selectFirst("li.status p.detail-info span")?.text().toStatus() - genre = document.select("li.category p.detail-info a")?.joinToString { it.text() } - thumbnail_url = imageOrNull(document.selectFirst("img.image-comic")!!) - } + override fun mangaDetailsParse(document: Document) = + SManga.create().apply { + title = document.selectFirst("h1.title-manga")!!.text() + description = document.selectFirst("p.detail-summary")?.text() + status = document.selectFirst("li.status p.detail-info span")?.text().toStatus() + genre = document.select("li.category p.detail-info a")?.joinToString { it.text() } + thumbnail_url = imageOrNull(document.selectFirst("img.image-comic")!!) + } override fun chapterListSelector() = "div.list-chapter li.row:not(.heading):not([style])" - override fun chapterFromElement(element: Element): SChapter { - return super.chapterFromElement(element).apply { + override fun chapterFromElement(element: Element): SChapter = + super.chapterFromElement(element).apply { date_upload = element.select(".chapters + div").text().toDate() } - } override val genresSelector = ".categories-detail ul.nav li:not(.active) a" }