diff --git a/multisrc/overrides/madara/templescanesp/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/templescanesp/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index aa68ca46e..000000000 Binary files a/multisrc/overrides/madara/templescanesp/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/templescanesp/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/templescanesp/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 3be9c027a..000000000 Binary files a/multisrc/overrides/madara/templescanesp/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/templescanesp/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/templescanesp/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 8d84cda9f..000000000 Binary files a/multisrc/overrides/madara/templescanesp/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/templescanesp/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/templescanesp/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index c709dc5a3..000000000 Binary files a/multisrc/overrides/madara/templescanesp/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/templescanesp/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/templescanesp/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index b093413db..000000000 Binary files a/multisrc/overrides/madara/templescanesp/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/templescanesp/res/web_hi_res_512.png b/multisrc/overrides/madara/templescanesp/res/web_hi_res_512.png deleted file mode 100644 index b15248d65..000000000 Binary files a/multisrc/overrides/madara/templescanesp/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/templescanesp/src/TempleScanEsp.kt b/multisrc/overrides/madara/templescanesp/src/TempleScanEsp.kt deleted file mode 100644 index 3c5318afa..000000000 --- a/multisrc/overrides/madara/templescanesp/src/TempleScanEsp.kt +++ /dev/null @@ -1,103 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.templescanesp - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import okhttp3.FormBody -import okhttp3.Request -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class TempleScanEsp : Madara( - "TempleScan", - "https://templescanesp.com", - "es", - SimpleDateFormat("dd.MM.yyyy", Locale("es")), -) { - override val mangaSubString = "series" - - override fun popularMangaSelector() = "div:has(> div#series-card)" - override val popularMangaUrlSelector = "div#series-card a.series-link" - override fun popularMangaNextPageSelector() = "body:not(:has(.no-posts))" - - override val mangaDetailsSelectorAuthor = "div.post-content_item:contains(Autor) div.summary-content" - override val mangaDetailsSelectorArtist = "div.post-content_item:contains(Artista) div.summary-content" - override val mangaDetailsSelectorStatus = "div.post-content_item:contains(Estado) div.summary-content" - - private fun loadMoreRequest(page: Int, metaKey: String): Request { - val formBody = FormBody.Builder().apply { - add("action", "madara_load_more") - add("page", page.toString()) - add("template", "madara-core/content/content-archive") - add("vars[paged]", "1") - add("vars[orderby]", "meta_value_num") - add("vars[template]", "archive") - add("vars[sidebar]", "full") - add("vars[meta_query][0][0][key]", "_wp_manga_chapter_type") - add("vars[meta_query][0][0][value]", "manga") - add("vars[meta_query][0][relation]", "AND") - add("vars[meta_query][relation]", "AND") - add("vars[post_type]", "wp-manga") - add("vars[post_status]", "publish") - add("vars[meta_key]", metaKey) - add("vars[manga_archives_item_layout]", "big_thumbnail") - }.build() - - val xhrHeaders = headersBuilder() - .add("Content-Length", formBody.contentLength().toString()) - .add("Content-Type", formBody.contentType().toString()) - .add("X-Requested-With", "XMLHttpRequest") - .build() - - return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, formBody) - } - - override fun popularMangaRequest(page: Int): Request { - return loadMoreRequest(page - 1, "_wp_manga_views") - } - - override fun latestUpdatesRequest(page: Int): Request { - return loadMoreRequest(page - 1, "_latest_update") - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - with(element) { - select(popularMangaUrlSelector).first()?.let { - manga.setUrlWithoutDomain(it.attr("abs:href")) - } - - select("div.series-box .series-title").first()?.let { - manga.title = it.text() - } - - select("img").first()?.let { - manga.thumbnail_url = imageFromElement(it) - } - } - - return manga - } - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - - with(element) { - select(chapterUrlSelector).first()?.let { urlElement -> - chapter.url = urlElement.attr("abs:href").let { - it.substringBefore("?style=paged") + if (!it.endsWith(chapterUrlSuffix)) chapterUrlSuffix else "" - } - chapter.name = urlElement.select("p").text() - } - - chapter.date_upload = select("img:not(.thumb)").firstOrNull()?.attr("alt")?.let { parseRelativeDate(it) } - ?: select("span a").firstOrNull()?.attr("title")?.let { parseRelativeDate(it) } - ?: parseChapterDate(select(chapterDateSelector()).firstOrNull()?.text()) - } - - return chapter - } -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt index 002dfc53d..f97f0df12 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt @@ -466,7 +466,6 @@ class MadaraGenerator : ThemeSourceGenerator { SingleLang("Tatakae Scan", "https://tatakaescan.com", "pt-BR", isNsfw = true, overrideVersionCode = 2), SingleLang("Taurus Fansub", "https://taurusmanga.com", "es", overrideVersionCode = 1), SingleLang("TeenManhua", "https://teenmanhua.com", "en", overrideVersionCode = 1), - SingleLang("TempleScan", "https://templescanesp.com", "es", isNsfw = true, className = "TempleScanEsp", overrideVersionCode = 1), SingleLang("The Beginning After The End", "https://www.thebeginningaftertheend.fr", "fr", overrideVersionCode = 1), SingleLang("The Guild", "https://theguildscans.com", "en"), SingleLang("Time Naight", "https://timenaight.com", "tr"), diff --git a/src/es/templescanesp/AndroidManifest.xml b/src/es/templescanesp/AndroidManifest.xml new file mode 100644 index 000000000..8072ee00d --- /dev/null +++ b/src/es/templescanesp/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/src/es/templescanesp/build.gradle b/src/es/templescanesp/build.gradle new file mode 100644 index 000000000..e6d2f50b1 --- /dev/null +++ b/src/es/templescanesp/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'Temple Scan' + pkgNameSuffix = 'es.templescanesp' + extClass = '.TempleScanEsp' + extVersionCode = 33 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/es/templescanesp/res/mipmap-hdpi/ic_launcher.png b/src/es/templescanesp/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..0e3fb7a52 Binary files /dev/null and b/src/es/templescanesp/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/es/templescanesp/res/mipmap-mdpi/ic_launcher.png b/src/es/templescanesp/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..282c12a4d Binary files /dev/null and b/src/es/templescanesp/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/es/templescanesp/res/mipmap-xhdpi/ic_launcher.png b/src/es/templescanesp/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..50ea886d7 Binary files /dev/null and b/src/es/templescanesp/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/es/templescanesp/res/mipmap-xxhdpi/ic_launcher.png b/src/es/templescanesp/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..6cd4a9e3f Binary files /dev/null and b/src/es/templescanesp/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/es/templescanesp/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/templescanesp/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..3ec4e450e Binary files /dev/null and b/src/es/templescanesp/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/es/templescanesp/res/web_hi_res_512.png b/src/es/templescanesp/res/web_hi_res_512.png new file mode 100644 index 000000000..58e3613f9 Binary files /dev/null and b/src/es/templescanesp/res/web_hi_res_512.png differ diff --git a/src/es/templescanesp/src/eu/kanade/tachiyomi/extension/es/templescanesp/TempleScanEsp.kt b/src/es/templescanesp/src/eu/kanade/tachiyomi/extension/es/templescanesp/TempleScanEsp.kt new file mode 100644 index 000000000..610b63b40 --- /dev/null +++ b/src/es/templescanesp/src/eu/kanade/tachiyomi/extension/es/templescanesp/TempleScanEsp.kt @@ -0,0 +1,185 @@ +package eu.kanade.tachiyomi.extension.es.templescanesp + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost +import eu.kanade.tachiyomi.source.model.Filter +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.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy +import java.lang.IllegalArgumentException +import java.util.Calendar + +class TempleScanEsp : ParsedHttpSource() { + + override val name = "Temple Scan" + + override val baseUrl = "https://templescanesp.net" + + override val lang = "es" + + // Moved from Madara to individual extension + override val versionId: Int = 2 + + override val supportsLatest = true + + override val client: OkHttpClient = network.client.newBuilder() + .rateLimitHost(baseUrl.toHttpUrl(), 2) + .build() + + override fun headersBuilder(): Headers.Builder = Headers.Builder() + .add("Referer", baseUrl) + + private val json: Json by injectLazy() + + override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers) + + override fun popularMangaSelector(): String = "div#div-diario figure, div#div-semanal figure, div#div-mensual figure" + + override fun popularMangaNextPageSelector(): String? = null + + override fun popularMangaParse(response: Response): MangasPage { + val mangasPage = super.popularMangaParse(response) + val distinctList = mangasPage.mangas.distinctBy { it.url } + + return MangasPage(distinctList, mangasPage.hasNextPage) + } + + override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { + thumbnail_url = element.selectFirst("img")!!.attr("abs:src") + title = element.selectFirst("figcaption")!!.text() + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + } + + override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, headers) + + override fun latestUpdatesSelector(): String = "section.flex > div.grid > figure" + + override fun latestUpdatesNextPageSelector(): String? = null + + override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { + thumbnail_url = element.selectFirst("img")!!.attr("abs:src") + title = element.selectFirst("figcaption")!!.text() + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + if (query.isNotEmpty()) { + if (query.length > 1) return GET("$baseUrl/comics#$query", headers) + throw Exception("La búsqueda debe tener al menos 2 caracteres") + } + return GET("$baseUrl/comics?page=$page", headers) + } + + override fun searchMangaSelector(): String = "section.flex > div.grid > figure" + + override fun searchMangaNextPageSelector(): String = "nav > ul.pagination > li > a[rel=next]" + + override fun searchMangaParse(response: Response): MangasPage { + val query = response.request.url.fragment ?: return super.searchMangaParse(response) + val document = response.asJsoup() + val mangas = parseMangaList(document, query) + return MangasPage(mangas, false) + } + + private fun parseMangaList(document: Document, query: String): List { + val docString = document.toString() + val mangaListJson = JSON_PROJECT_LIST.find(docString)?.destructured?.toList()?.get(0).orEmpty() + + return try { + json.decodeFromString>(mangaListJson) + .filter { it.title.contains(query, ignoreCase = true) } + .map { + SManga.create().apply { + title = it.title + thumbnail_url = it.thumbnail + url = "/comic/${it.slug}" + } + } + } catch (_: IllegalArgumentException) { + emptyList() + } + } + + override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { + thumbnail_url = element.selectFirst("img")!!.attr("abs:src") + title = element.selectFirst("figcaption")!!.text() + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + } + + override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { + with(document.select("section#section-sinopsis")) { + description = select("p").text() + genre = select("div.flex:has(div:containsOwn(Genders)) > div > a > span").joinToString { it.text() } + author = select("div.flex:has(div:containsOwn(Autor)) > div").text() + } + } + + override fun chapterListSelector(): String = "section#section-list-cap div.grid-capitulos > div" + + override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + name = element.selectFirst("div#name")!!.text() + date_upload = parseRelativeDate(element.selectFirst("time")!!.text()) + } + + override fun pageListParse(document: Document): List { + return document.select("main.contenedor-imagen > section img[src]").mapIndexed { i, element -> + Page(i, "", element.attr("abs:src")) + } + } + + override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used!") + + override fun getFilterList(): FilterList { + return FilterList( + Filter.Header("Limpie la barra de búsqueda y haga click en 'Filtrar' para mostrar todas las series."), + ) + } + + private fun parseRelativeDate(date: String): Long { + val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 + val cal = Calendar.getInstance() + + return when { + WordSet("segundo", "second").anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis + WordSet("minuto", "minute").anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + WordSet("hora", "hour").anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + WordSet("día", "dia", "day").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + WordSet("semana", "week").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number * 7) }.timeInMillis + WordSet("mes", "month").anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + WordSet("año", "year").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis + else -> 0 + } + } + + class WordSet(private vararg val words: String) { + fun anyWordIn(dateString: String): Boolean = words.any { dateString.contains(it, ignoreCase = true) } + } + + @Serializable + data class SerieDto( + @SerialName("nombre") val title: String, + val slug: String, + @SerialName("portada") val thumbnail: String, + ) + + companion object { + private val JSON_PROJECT_LIST = """proyectos\s*=\s*(\[[\s\S]+?\])\s*;""".toRegex() + } +}