Use TaoSect's API in the extension. ()

This commit is contained in:
Alessandro Jean 2021-09-19 19:42:44 -03:00 committed by GitHub
parent a9a6c9f2c0
commit f267b0f5d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 345 additions and 160 deletions
src/pt/taosect
build.gradle
src/eu/kanade/tachiyomi/extension/pt/taosect

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'Tao Sect' extName = 'Tao Sect'
pkgNameSuffix = 'pt.taosect' pkgNameSuffix = 'pt.taosect'
extClass = '.TaoSect' extClass = '.TaoSect'
extVersionCode = 7 extVersionCode = 8
} }
dependencies { dependencies {

@ -3,26 +3,31 @@ package eu.kanade.tachiyomi.extension.pt.taosect
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.Jsoup
import org.jsoup.nodes.Element import org.jsoup.parser.Parser
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class TaoSect : ParsedHttpSource() { class TaoSect : HttpSource() {
override val name = "Tao Sect" override val name = "Tao Sect"
@ -30,179 +35,309 @@ class TaoSect : ParsedHttpSource() {
override val lang = "pt-BR" override val lang = "pt-BR"
override val supportsLatest = false override val supportsLatest = true
override val client: OkHttpClient = network.client.newBuilder() override val client: OkHttpClient = network.client.newBuilder()
.addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS)) .addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
.build() .build()
private val json: Json by injectLazy()
private val apiHeaders: Headers by lazy { apiHeadersBuilder().build() }
override fun headersBuilder(): Headers.Builder = Headers.Builder() override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("User-Agent", USER_AGENT) .add("User-Agent", USER_AGENT)
.add("Origin", baseUrl) .add("Origin", baseUrl)
.add("Referer", baseUrl) .add("Referer", baseUrl)
private fun apiHeadersBuilder(): Headers.Builder = headersBuilder()
.add("Accept", ACCEPT_JSON)
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/situacao/ativos", headers) val apiUrl = "$baseUrl/$API_BASE_PATH/projetos".toHttpUrl().newBuilder()
.addQueryParameter("order", "desc")
.addQueryParameter("orderby", "views")
.addQueryParameter("page", page.toString())
.addQueryParameter("per_page", PROJECTS_PER_PAGE.toString())
.addQueryParameter("_fields", DEFAULT_FIELDS)
.toString()
return GET(apiUrl, apiHeaders)
} }
override fun popularMangaSelector(): String = "div.post-list article.post-projeto" override fun popularMangaParse(response: Response): MangasPage {
val result = json.decodeFromString<List<TaoSectProjectDto>>(response.body!!.string())
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { val projectList = result.map(::popularMangaFromObject)
val sibling = element.nextElementSibling()!!
title = sibling.select("h3.titulo-popover").text()!! val currentPage = response.request.url.queryParameter("page")!!.toInt()
thumbnail_url = element.select("div.post-projeto-background")!! val lastPage = response.headers["X-Wp-TotalPages"]!!.toInt()
.attr("style") val hasNextPage = currentPage < lastPage
.substringAfter("url(")
.substringBefore(");") return MangasPage(projectList, hasNextPage)
setUrlWithoutDomain(element.select("a[title]").first()!!.attr("href"))
} }
override fun popularMangaNextPageSelector(): String? = null private fun popularMangaFromObject(obj: TaoSectProjectDto): SManga = SManga.create().apply {
title = Parser.unescapeEntities(obj.title!!.rendered, true)
thumbnail_url = obj.thumbnail
setUrlWithoutDomain(obj.link!!)
}
override fun latestUpdatesRequest(page: Int): Request {
val apiUrl = "$baseUrl/$API_BASE_PATH/capitulos".toHttpUrl().newBuilder()
.addQueryParameter("order", "desc")
.addQueryParameter("orderby", "date")
.addQueryParameter("page", page.toString())
.addQueryParameter("per_page", PROJECTS_PER_PAGE.toString())
.toString()
return GET(apiUrl, apiHeaders)
}
override fun latestUpdatesParse(response: Response): MangasPage {
val result = json.decodeFromString<List<TaoSectChapterDto>>(response.body!!.string())
if (result.isNullOrEmpty()) {
return MangasPage(emptyList(), hasNextPage = false)
}
val projectIds = result
.distinctBy { it.projectId!! }
.joinToString(",") { it.projectId!! }
val projectsApiUrl = "$baseUrl/$API_BASE_PATH/projetos".toHttpUrl().newBuilder()
.addQueryParameter("include", projectIds)
.addQueryParameter("orderby", "include")
.addQueryParameter("_fields", DEFAULT_FIELDS)
.toString()
val projectsRequest = GET(projectsApiUrl, apiHeaders)
val projectsResponse = client.newCall(projectsRequest).execute()
val projectsResult = json.decodeFromString<List<TaoSectProjectDto>>(projectsResponse.body!!.string())
val projectList = projectsResult.map(::popularMangaFromObject)
val currentPage = response.request.url.queryParameter("page")!!.toInt()
val lastPage = response.headers["X-Wp-TotalPages"]!!.toInt()
val hasNextPage = currentPage < lastPage
projectsResponse.close()
return MangasPage(projectList, hasNextPage)
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/pesquisar-leitor".toHttpUrlOrNull()!!.newBuilder() val apiUrl = "$baseUrl/$API_BASE_PATH/projetos".toHttpUrl().newBuilder()
.addQueryParameter("leitor_titulo_projeto", query) .addQueryParameter("page", page.toString())
.addQueryParameter("per_page", PROJECTS_PER_PAGE.toString())
.addQueryParameter("search", query)
.addQueryParameter("_fields", DEFAULT_FIELDS)
filters.forEach { filter -> filters.forEach { filter ->
when (filter) { when (filter) {
is CountryFilter -> { is CountryFilter -> {
filter.state filter.state
.filter { it.state } .groupBy { it.state }
.forEach { url.addQueryParameter("leitor_pais_projeto[]", it.id) } .entries
.forEach { entry ->
val values = entry.value.joinToString(",") { it.id }
if (entry.key == Filter.TriState.STATE_EXCLUDE) {
apiUrl.addQueryParameter("paises_exclude", values)
} else if (entry.key == Filter.TriState.STATE_INCLUDE) {
apiUrl.addQueryParameter("paises", values)
}
}
} }
is StatusFilter -> { is StatusFilter -> {
filter.state filter.state
.filter { it.state } .groupBy { it.state }
.forEach { url.addQueryParameter("leitor_status_projeto[]", it.id) } .entries
.forEach { entry ->
val values = entry.value.joinToString(",") { it.id }
if (entry.key == Filter.TriState.STATE_EXCLUDE) {
apiUrl.addQueryParameter("situacao_exclude", values)
} else if (entry.key == Filter.TriState.STATE_INCLUDE) {
apiUrl.addQueryParameter("situacao", values)
}
}
} }
is GenreFilter -> { is GenreFilter -> {
filter.state.forEach { genre -> filter.state
if (genre.isIncluded()) { .groupBy { it.state }
url.addQueryParameter("leitor_tem_genero_projeto[]", genre.id) .entries
} else if (genre.isExcluded()) { .forEach { entry ->
url.addQueryParameter("leitor_n_tem_genero_projeto[]", genre.id) val values = entry.value.joinToString(",") { it.id }
if (entry.key == Filter.TriState.STATE_EXCLUDE) {
apiUrl.addQueryParameter("generos_exclude", values)
} else if (entry.key == Filter.TriState.STATE_INCLUDE) {
apiUrl.addQueryParameter("generos", values)
} }
} }
} }
is SortFilter -> { is SortFilter -> {
val sort = when { val orderBy = if (filter.state == null) SORT_LIST[DEFAULT_ORDERBY].id else
filter.state == null -> "a_z" SORT_LIST[filter.state!!.index].id
filter.state!!.ascending -> SORT_LIST[filter.state!!.index].first val order = if (filter.state?.ascending == true) "asc" else "desc"
else -> SORT_LIST[filter.state!!.index].second
}
url.addQueryParameter("leitor_ordem_projeto", sort) apiUrl.addQueryParameter("order", order)
apiUrl.addQueryParameter("orderby", orderBy)
} }
} }
} }
return GET(url.toString(), headers) return GET(apiUrl.toString(), apiHeaders)
} }
override fun searchMangaSelector() = "article.manga_item" override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { // Workaround to allow "Open in browser" use the real URL.
title = element.select("h2.titulo_manga_item a")!!.text() override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
thumbnail_url = element.select("div.container_imagem")!! return client.newCall(mangaDetailsApiRequest(manga))
.attr("style") .asObservableSuccess()
.substringAfter("url(") .map { response ->
.substringBefore(");") mangaDetailsParse(response).apply { initialized = true }
setUrlWithoutDomain(element.select("h2.titulo_manga_item a")!!.attr("href")) }
} }
override fun searchMangaNextPageSelector(): String? = null private fun mangaDetailsApiRequest(manga: SManga): Request {
val projectSlug = manga.url
.substringAfterLast("projeto/")
.substringBefore("/")
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { val apiUrl = "$baseUrl/$API_BASE_PATH/projetos".toHttpUrl().newBuilder()
val header = document.select("div.cabelho-projeto").first()!! .addQueryParameter("per_page", "1")
.addQueryParameter("slug", projectSlug)
.addQueryParameter("_fields", "title,informacoes,content,thumbnail")
.toString()
title = header.select("h1.titulo-projeto")!!.text() return GET(apiUrl, apiHeaders)
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(11) a").joinToString { it.text() } override fun mangaDetailsParse(response: Response): SManga {
status = header.select("table.tabela-projeto tr:eq(5) td:eq(1)")!!.text().toStatus() val result = json.decodeFromString<List<TaoSectProjectDto>>(response.body!!.string())
description = header.select("table.tabela-projeto tr:eq(10) p")!!.text()
thumbnail_url = header.select("div.imagens-projeto img[alt]").first()!!.attr("data-src") if (result.isNullOrEmpty()) {
throw Exception(PROJECT_NOT_FOUND)
}
val project = result[0]
return SManga.create().apply {
title = Parser.unescapeEntities(project.title!!.rendered, true)
author = project.info!!.script
artist = project.info.art
genre = project.info.genres.joinToString { it.name }
status = project.info.status!!.name.toStatus()
description = Jsoup.parse(project.content!!.rendered).text() +
"\n\nTítulo original: " + project.info.originalTitle +
"\nSerialização: " + project.info.serialization
thumbnail_url = project.thumbnail
}
}
override fun chapterListRequest(manga: SManga): Request {
val projectSlug = manga.url
.substringAfterLast("projeto/")
.substringBefore("/")
val apiUrl = "$baseUrl/$API_BASE_PATH/projetos".toHttpUrl().newBuilder()
.addQueryParameter("per_page", "1")
.addQueryParameter("slug", projectSlug)
.addQueryParameter("_fields", "id,slug,capitulos")
.toString()
return GET(apiUrl, apiHeaders)
} }
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup() val result = json.decodeFromString<List<TaoSectProjectDto>>(response.body!!.string())
if (result.isNullOrEmpty()) {
throw Exception(PROJECT_NOT_FOUND)
}
val project = result[0]
// Count the project views, requested by the scanlator. // Count the project views, requested by the scanlator.
// The website counts the views every time a request is done to the project page, val countViewRequest = countViewRequest(project.id.toString())
// so to mimic this behavior, the count view request is sent in the chapterListParse,
// that will then get called every time in the global update.
val projectScript = document.selectFirst("script:containsData(dataAjax.url)").data()
val projectId = PROJECT_ID_REGEX.find(projectScript)?.groupValues?.get(1)
if (projectId.isNullOrBlank().not()) {
val countViewRequest = countViewRequest(document.location(), projectId!!)
runCatching { client.newCall(countViewRequest).execute().close() } runCatching { client.newCall(countViewRequest).execute().close() }
}
return document.select(chapterListSelector()) val timeNow = System.currentTimeMillis()
.map(::chapterFromElement)
return project.volumes!!
.flatMap { it.chapters }
.reversed() .reversed()
.map { chapterFromObject(it, project) }
.filter { it.date_upload <= timeNow }
} }
override fun chapterListSelector() = "table.tabela-volumes tr" private fun chapterFromObject(obj: TaoSectChapterDto, project: TaoSectProjectDto): SChapter = SChapter.create().apply {
name = obj.name
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
name = element.select("td[align='left'] a")!!.text()
scanlator = this@TaoSect.name scanlator = this@TaoSect.name
date_upload = element.select("td[align='right']")!!.text().toDate() date_upload = if (obj.releaseDate.isNullOrEmpty().not()) obj.releaseDate!!.toDate() else obj.date.toDate()
url = "/leitor-online/projeto/${project.slug!!}/${obj.slug}/"
// The page have a template problem and it's printing the end of the PHP echo command.
val fixedUrl = element.select("td[align='left'] a")!!
.attr("href")
.substringBeforeLast(";")
setUrlWithoutDomain(fixedUrl)
} }
override fun pageListRequest(chapter: SChapter): Request { override fun pageListRequest(chapter: SChapter): Request {
val projectUrl = "$baseUrl/" + chapter.url val projectSlug = chapter.url
.substringAfter("online/") .substringAfter("projeto/")
.substringBefore("/") .substringBefore("/")
val chapterSlug = chapter.url
.removeSuffix("/")
.substringAfterLast("/")
val newHeaders = headersBuilder() val apiUrl = "$baseUrl/$API_BASE_PATH/projetos".toHttpUrl().newBuilder()
.set("Referer", projectUrl) .addQueryParameter("per_page", "1")
.build() .addQueryParameter("slug", projectSlug)
.addQueryParameter("chapter_slug", chapterSlug)
.addQueryParameter("_fields", "id,slug,capitulos")
.toString()
return GET(baseUrl + chapter.url, newHeaders) return GET(apiUrl, apiHeaders)
} }
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(response: Response): List<Page> {
val readerScript = document.selectFirst("script:containsData(var paginas)")!!.data() val result = json.decodeFromString<List<TaoSectProjectDto>>(response.body!!.string())
if (result.isNullOrEmpty()) {
throw Exception(PROJECT_NOT_FOUND)
}
val project = result[0]
val chapterSlug = response.request.url.queryParameter("chapter_slug")!!
val chapter = project.volumes!!
.flatMap { it.chapters }
.firstOrNull { it.slug == chapterSlug }
?: throw Exception(CHAPTER_NOT_FOUND)
val chapterUrl = "$baseUrl/leitor-online/projeto/${project.slug!!}/${chapter.slug}"
// Count the chapter views, requested by the scanlator. // Count the chapter views, requested by the scanlator.
val projectId = PROJECT_ID_REGEX.find(readerScript)?.groupValues?.get(1) val countViewRequest = countViewRequest(project.id!!.toString(), chapter.id)
val chapterId = CHAPTER_ID_REGEX.find(readerScript)?.groupValues?.get(1)
if (projectId.isNullOrBlank().not() && chapterId.isNullOrEmpty().not()) {
val countViewRequest = countViewRequest(document.location(), projectId!!, chapterId!!)
runCatching { client.newCall(countViewRequest).execute().close() } runCatching { client.newCall(countViewRequest).execute().close() }
}
return readerScript return chapter.pages.mapIndexed { i, pageUrl ->
.substringAfter("var paginas = [") Page(i, chapterUrl, pageUrl)
.substringBefore("];")
.split(",")
.mapIndexed { i, url ->
Page(i, document.location(), url.replace("\"", ""))
} }
} }
override fun imageUrlParse(document: Document) = "" override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!)
override fun imageUrlParse(response: Response): String = ""
override fun imageRequest(page: Page): Request { override fun imageRequest(page: Page): Request {
val newHeaders = headersBuilder() val newHeaders = headersBuilder()
.add("Accept", ACCEPT_IMAGE)
.set("Referer", page.url) .set("Referer", page.url)
.build() .build()
return GET(page.imageUrl!!, newHeaders) return GET(page.imageUrl!!, newHeaders)
} }
private fun countViewRequest(chapterUrl: String, projectId: String, chapterId: String? = null): Request { private fun countViewRequest(projectId: String, chapterId: String? = null): Request {
val formBodyBuilder = FormBody.Builder() val formBodyBuilder = FormBody.Builder()
.add("action", "update_views") .add("action", "update_views")
.add("projeto", projectId) .add("projeto", projectId)
@ -216,7 +351,6 @@ class TaoSect : ParsedHttpSource() {
val newHeaders = headersBuilder() val newHeaders = headersBuilder()
.add("Content-Length", formBody.contentLength().toString()) .add("Content-Length", formBody.contentLength().toString())
.add("Content-Type", formBody.contentType().toString()) .add("Content-Type", formBody.contentType().toString())
.set("Referer", chapterUrl)
.build() .build()
return POST("$baseUrl/wp-admin/admin-ajax.php", newHeaders, formBody) return POST("$baseUrl/wp-admin/admin-ajax.php", newHeaders, formBody)
@ -224,35 +358,27 @@ class TaoSect : ParsedHttpSource() {
override fun getFilterList(): FilterList = FilterList( override fun getFilterList(): FilterList = FilterList(
CountryFilter(getCountryList()), CountryFilter(getCountryList()),
StatusFilter(getStatusList()), // Status filter is broken on the API at the moment.
// It will be fixed by the scanlator team.
// StatusFilter(getStatusList()),
GenreFilter(getGenreList()), GenreFilter(getGenreList()),
SortFilter() SortFilter()
) )
private class Tag(val id: String, name: String) : Filter.CheckBox(name) private class Tag(val id: String, name: String) : Filter.TriState(name)
private class Genre(val id: String, name: String) : Filter.TriState(name)
private class CountryFilter(countries: List<Tag>) : Filter.Group<Tag>("País", countries) private class CountryFilter(countries: List<Tag>) : Filter.Group<Tag>("País", countries)
private class StatusFilter(status: List<Tag>) : Filter.Group<Tag>("Status", status) private class StatusFilter(status: List<Tag>) : Filter.Group<Tag>("Status", status)
private class GenreFilter(genres: List<Genre>) : Filter.Group<Genre>("Gêneros", genres) private class GenreFilter(genres: List<Tag>) : Filter.Group<Tag>("Gêneros", genres)
private class SortFilter : Filter.Sort( private class SortFilter : Filter.Sort(
"Ordem", "Ordem",
SORT_LIST.map { it.third }.toTypedArray(), SORT_LIST.map { it.name }.toTypedArray(),
Selection(0, true) Selection(DEFAULT_ORDERBY, false)
) )
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 String.toDate(): Long { private fun String.toDate(): Long {
return runCatching { DATE_FORMATTER.parse(this)?.time } return runCatching { DATE_FORMATTER.parse(this)?.time }
.getOrNull() ?: 0L .getOrNull() ?: 0L
@ -277,54 +403,60 @@ class TaoSect : ParsedHttpSource() {
Tag("6", "One-shot") Tag("6", "One-shot")
) )
// [...document.querySelectorAll("#leitor_tem_genero_projeto option")] private fun getGenreList(): List<Tag> = listOf(
// .map(el => `Genre("${el.getAttribute("value")}", "${el.innerText}")`).join(',\n') Tag("31", "4Koma"),
private fun getGenreList(): List<Genre> = listOf( Tag("24", "Ação"),
Genre("31", "4Koma"), Tag("84", "Adulto"),
Genre("24", "Ação"), Tag("21", "Artes Marciais"),
Genre("84", "Adulto"), Tag("25", "Aventura"),
Genre("21", "Artes Marciais"), Tag("26", "Comédia"),
Genre("25", "Aventura"), Tag("66", "Culinária"),
Genre("26", "Comédia"), Tag("78", "Doujinshi"),
Genre("66", "Culinária"), Tag("22", "Drama"),
Genre("78", "Doujinshi"), Tag("12", "Ecchi"),
Genre("22", "Drama"), Tag("30", "Escolar"),
Genre("12", "Ecchi"), Tag("76", "Esporte"),
Genre("30", "Escolar"), Tag("23", "Fantasia"),
Genre("76", "Esporte"), Tag("29", "Harém"),
Genre("23", "Fantasia"), Tag("75", "Histórico"),
Genre("29", "Harém"), Tag("83", "Horror"),
Genre("75", "Histórico"), Tag("18", "Isekai"),
Genre("83", "Horror"), Tag("20", "Light Novel"),
Genre("18", "Isekai"), Tag("61", "Manhua"),
Genre("20", "Light Novel"), Tag("56", "Psicológico"),
Genre("61", "Manhua"), Tag("7", "Romance"),
Genre("56", "Psicológico"), Tag("27", "Sci-fi"),
Genre("7", "Romance"), Tag("28", "Seinen"),
Genre("27", "Sci-fi"), Tag("55", "Shoujo"),
Genre("28", "Seinen"), Tag("54", "Shounen"),
Genre("55", "Shoujo"), Tag("19", "Slice of life"),
Genre("54", "Shounen"), Tag("17", "Sobrenatural"),
Genre("19", "Slice of life"), Tag("57", "Tragédia"),
Genre("17", "Sobrenatural"), Tag("62", "Webtoon")
Genre("57", "Tragédia"),
Genre("62", "Webtoon")
) )
companion object { companion object {
private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36" private const val ACCEPT_JSON = "application/json"
private val USER_AGENT = "Tachiyomi " + System.getProperty("http.agent")
private const val API_BASE_PATH = "wp-json/wp/v2"
private const val PROJECTS_PER_PAGE = 18
private const val DEFAULT_ORDERBY = 4
private const val DEFAULT_FIELDS = "title,thumbnail,link"
private const val PROJECT_NOT_FOUND = "Projeto não encontrado."
private const val CHAPTER_NOT_FOUND = "Capítulo não encontrado."
private val DATE_FORMATTER by lazy { private val DATE_FORMATTER by lazy {
SimpleDateFormat("(dd/MM/yyyy)", Locale.ENGLISH) SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH)
} }
private val SORT_LIST = listOf( private val SORT_LIST = listOf(
Triple("a_z", "z_a", "Nome"), Tag("date", "Data de criação"),
Triple("dt_asc", "date-desc", "Data") Tag("modified", "Data de modificação"),
Tag("destaque", "Destaque"),
Tag("title", "Título"),
Tag("views", "Visualizações")
) )
private val PROJECT_ID_REGEX = "projeto: '(\\d+)',?".toRegex()
private val CHAPTER_ID_REGEX = "capitulo: '(\\d+)',?".toRegex()
} }
} }

@ -0,0 +1,52 @@
package eu.kanade.tachiyomi.extension.pt.taosect
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class TaoSectProjectDto(
val content: TaoSectContentDto? = null,
val id: Int? = -1,
@SerialName("informacoes") val info: TaoSectProjectInfoDto? = null,
val link: String? = "",
val slug: String? = "",
val title: TaoSectContentDto? = null,
val thumbnail: String? = "",
@SerialName("capitulos") val volumes: List<TaoSectVolumeDto>? = emptyList()
)
@Serializable
data class TaoSectContentDto(
val rendered: String = ""
)
@Serializable
data class TaoSectProjectInfoDto(
@SerialName("arte") val art: String = "",
@SerialName("generos") val genres: List<TaoSectTagDto> = emptyList(),
@SerialName("titulo_pais_origem") val originalTitle: String = "",
@SerialName("roteiro") val script: String = "",
@SerialName("serializacao") val serialization: String = "",
@SerialName("status_scan") val status: TaoSectTagDto? = null
)
@Serializable
data class TaoSectTagDto(
@SerialName("nome") val name: String = ""
)
@Serializable
data class TaoSectVolumeDto(
@SerialName("capitulos") val chapters: List<TaoSectChapterDto> = emptyList()
)
@Serializable
data class TaoSectChapterDto(
@SerialName("data_insercao") val date: String = "",
@SerialName("id_capitulo") val id: String = "",
@SerialName("nome_capitulo") val name: String = "",
@SerialName("paginas") val pages: List<String> = emptyList(),
@SerialName("post_id") val projectId: String? = "",
@SerialName("data_hora_agendamento") val releaseDate: String? = "",
val slug: String = ""
)