diff --git a/src/pt/taosect/build.gradle b/src/pt/taosect/build.gradle new file mode 100644 index 000000000..0331c7fa2 --- /dev/null +++ b/src/pt/taosect/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'Tao Sect' + pkgNameSuffix = 'pt.taosect' + extClass = '.TaoSect' + extVersionCode = 1 + libVersion = '1.2' +} + +apply from: "$rootDir/common.gradle" diff --git a/src/pt/taosect/res/mipmap-hdpi/ic_launcher.png b/src/pt/taosect/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..c0a27c960 Binary files /dev/null and b/src/pt/taosect/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/pt/taosect/res/mipmap-mdpi/ic_launcher.png b/src/pt/taosect/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..7640239d1 Binary files /dev/null and b/src/pt/taosect/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/pt/taosect/res/mipmap-xhdpi/ic_launcher.png b/src/pt/taosect/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..d642ce103 Binary files /dev/null and b/src/pt/taosect/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/pt/taosect/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/taosect/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..a2f794d2a Binary files /dev/null and b/src/pt/taosect/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/pt/taosect/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/taosect/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..dbd743759 Binary files /dev/null and b/src/pt/taosect/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/pt/taosect/res/web_hi_res_512.png b/src/pt/taosect/res/web_hi_res_512.png new file mode 100644 index 000000000..e3052b9e8 Binary files /dev/null and b/src/pt/taosect/res/web_hi_res_512.png differ diff --git a/src/pt/taosect/src/eu/kanade/tachiyomi/extension/pt/taosect/TaoSect.kt b/src/pt/taosect/src/eu/kanade/tachiyomi/extension/pt/taosect/TaoSect.kt new file mode 100644 index 000000000..9eb625755 --- /dev/null +++ b/src/pt/taosect/src/eu/kanade/tachiyomi/extension/pt/taosect/TaoSect.kt @@ -0,0 +1,265 @@ +package eu.kanade.tachiyomi.extension.pt.taosect + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import java.lang.UnsupportedOperationException +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.Locale +import okhttp3.Headers +import okhttp3.HttpUrl +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element + +class TaoSect : ParsedHttpSource() { + + override val name = "Tao Sect" + + override val baseUrl = "https://taosect.com" + + override val lang = "pt-BR" + + override val supportsLatest = false + + override fun headersBuilder(): Headers.Builder = Headers.Builder() + .add("User-Agent", USER_AGENT) + .add("Origin", baseUrl) + .add("Referer", baseUrl) + + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/situacao/ativos", headers) + } + + override fun popularMangaSelector(): String = "div.post-list article.post-projeto" + + override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { + val sibling = element.nextElementSibling()!! + + title = sibling.select("h3.titulo-popover").text()!! + thumbnail_url = element.select("div.post-projeto-background")!! + .attr("style") + .substringAfter("url(") + .substringBefore(");") + setUrlWithoutDomain(element.select("a[title]").first()!!.attr("href")) + } + + override fun popularMangaNextPageSelector(): String? = null + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = HttpUrl.parse("$baseUrl/pesquisar-leitor")!!.newBuilder() + .addQueryParameter("leitor_titulo_projeto", query) + + filters.forEach { filter -> + when (filter) { + is CountryFilter -> { + filter.state + .filter { it.state } + .forEach { url.addQueryParameter("leitor_pais_projeto[]", it.id) } + } + is StatusFilter -> { + filter.state + .filter { it.state } + .forEach { url.addQueryParameter("leitor_status_projeto[]", it.id) } + } + is GenreFilter -> { + filter.state.forEach { genre -> + if (genre.isIncluded()) { + url.addQueryParameter("leitor_tem_genero_projeto[]", genre.id) + } else if (genre.isExcluded()) { + url.addQueryParameter("leitor_n_tem_genero_projeto[]", genre.id) + } + } + } + is SortFilter -> { + val sort = when { + filter.state == null -> "a_z" + filter.state!!.ascending -> SORT_LIST[filter.state!!.index].first + else -> SORT_LIST[filter.state!!.index].second + } + + url.addQueryParameter("leitor_ordem_projeto", sort) + } + } + } + + return GET(url.toString(), headers) + } + + override fun searchMangaSelector() = "article.manga_item" + + override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { + title = element.select("h2.titulo_manga_item a")!!.text() + thumbnail_url = element.select("div.container_imagem")!! + .attr("style") + .substringAfter("url(") + .substringBefore(");") + setUrlWithoutDomain(element.select("h2.titulo_manga_item a")!!.attr("href")) + } + + override fun searchMangaNextPageSelector(): String? = null + + override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { + val header = document.select("div.cabelho-projeto").first()!! + + title = header.select("h1.titulo-projeto")!!.text() + author = header.select("table.tabela-projeto tr:eq(1) td:eq(1)")!!.text() + artist = header.select("table.tabela-projeto tr:eq(0) td:eq(1)")!!.text() + genre = header.select("table.tabela-projeto tr:eq(10) a").joinToString { it.text() } + status = header.select("table.tabela-projeto tr:eq(4) td:eq(1)")!!.text().toStatus() + description = header.select("table.tabela-projeto tr:eq(9) p")!!.text() + thumbnail_url = header.select("div.imagens-projeto img[alt]").first()!!.attr("src") + } + + override fun chapterListParse(response: Response): List { + return super.chapterListParse(response).reversed() + } + + override fun chapterListSelector() = "table.tabela-volumes tr:not(:first-child)" + + override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { + name = element.select("td[align='left'] a")!!.text() + scanlator = this@TaoSect.name + date_upload = DATE_FORMATTER.tryParseTime(element.select("td[align='right']")!!.text()) + setUrlWithoutDomain(element.select("td[align='left'] a")!!.attr("href")) + } + + override fun pageListRequest(chapter: SChapter): Request { + val projectUrl = "$baseUrl/" + chapter.url + .substringAfter("online/") + .substringBefore("/") + + val newHeaders = headersBuilder() + .set("Referer", projectUrl) + .build() + + return GET(baseUrl + chapter.url, newHeaders) + } + + override fun pageListParse(document: Document): List { + return document.select("script:containsData(var paginas)").first()!! + .data() + .substringAfter("var paginas = [") + .substringBefore("];") + .split(",") + .mapIndexed { i, url -> + Page(i, document.location(), url.replace("\"", "")) + } + } + + override fun imageUrlParse(document: Document) = "" + + override fun imageRequest(page: Page): Request { + val newHeaders = headersBuilder() + .set("Referer", page.url) + .build() + + return GET(page.imageUrl!!, newHeaders) + } + + override fun getFilterList(): FilterList = FilterList( + CountryFilter(getCountryList()), + StatusFilter(getStatusList()), + GenreFilter(getGenreList()), + SortFilter() + ) + + private class Tag(val id: String, name: String) : Filter.CheckBox(name) + + private class Genre(val id: String, name: String) : Filter.TriState(name) + + private class CountryFilter(countries: List) : Filter.Group("País", countries) + + private class StatusFilter(status: List) : Filter.Group("Status", status) + + private class GenreFilter(genres: List) : Filter.Group("Gêneros", genres) + + private class SortFilter : Filter.Sort("Ordem", + SORT_LIST.map { it.third }.toTypedArray(), Selection(0, true)) + + override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") + + override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not used") + + override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") + + override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used") + + private fun SimpleDateFormat.tryParseTime(date: String): Long { + return try { + parse(date)!!.time + } catch (e: ParseException) { + 0L + } + } + + private fun String.toStatus() = when { + contains("Ativo") -> SManga.ONGOING + contains("Finalizado") || contains("Oneshots") -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + + private fun getCountryList(): List = listOf( + Tag("59", "China"), + Tag("60", "Coréia do Sul"), + Tag("13", "Japão") + ) + + private fun getStatusList(): List = listOf( + Tag("3", "Ativo"), + Tag("5", "Cancelado"), + Tag("4", "Finalizado"), + Tag("6", "One-shot") + ) + + // [...document.querySelectorAll("#leitor_tem_genero_projeto option")] + // .map(el => `Genre("${el.getAttribute("value")}", "${el.innerText}")`).join(',\n') + private fun getGenreList(): List = listOf( + Genre("31", "4Koma"), + Genre("24", "Ação"), + Genre("84", "Adulto"), + Genre("21", "Artes Marciais"), + Genre("25", "Aventura"), + Genre("26", "Comédia"), + Genre("66", "Culinária"), + Genre("78", "Doujinshi"), + Genre("22", "Drama"), + Genre("12", "Ecchi"), + Genre("30", "Escolar"), + Genre("76", "Esporte"), + Genre("23", "Fantasia"), + Genre("29", "Harém"), + Genre("75", "Histórico"), + Genre("83", "Horror"), + Genre("18", "Isekai"), + Genre("20", "Light Novel"), + Genre("61", "Manhua"), + Genre("56", "Psicológico"), + Genre("7", "Romance"), + Genre("27", "Sci-fi"), + Genre("28", "Seinen"), + Genre("55", "Shoujo"), + Genre("54", "Shounen"), + Genre("19", "Slice of life"), + Genre("17", "Sobrenatural"), + Genre("57", "Tragédia"), + Genre("62", "Webtoon") + ) + + companion object { + private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" + + private val DATE_FORMATTER by lazy { SimpleDateFormat("(dd/MM/yyyy)", Locale.ENGLISH) } + + private val SORT_LIST = listOf( + Triple("a_z", "z_a", "Nome"), + Triple("dt_asc", "date-desc", "Data") + ) + } +}