From 723dd523fe6f20e7d5e53b8e10a5a0bb4bccc8f2 Mon Sep 17 00:00:00 2001 From: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> Date: Sat, 29 Jul 2023 07:33:07 +0500 Subject: [PATCH] Temple Scan: migrate to individual extension (#17294) --- .../madara/templescan/src/TempleScan.kt | 47 ----- .../multisrc/madara/MadaraGenerator.kt | 1 - src/en/templescan/AndroidManifest.xml | 24 +++ src/en/templescan/build.gradle | 12 ++ .../res/mipmap-hdpi/ic_launcher.png | Bin .../res/mipmap-mdpi/ic_launcher.png | Bin .../res/mipmap-xhdpi/ic_launcher.png | Bin .../res/mipmap-xxhdpi/ic_launcher.png | Bin .../res/mipmap-xxxhdpi/ic_launcher.png | Bin .../en}/templescan/res/web_hi_res_512.png | Bin .../extension/en/templescan/TempleScan.kt | 199 ++++++++++++++++++ .../en/templescan/TempleScanUrlActivity.kt | 34 +++ 12 files changed, 269 insertions(+), 48 deletions(-) delete mode 100644 multisrc/overrides/madara/templescan/src/TempleScan.kt create mode 100644 src/en/templescan/AndroidManifest.xml create mode 100644 src/en/templescan/build.gradle rename {multisrc/overrides/madara => src/en}/templescan/res/mipmap-hdpi/ic_launcher.png (100%) rename {multisrc/overrides/madara => src/en}/templescan/res/mipmap-mdpi/ic_launcher.png (100%) rename {multisrc/overrides/madara => src/en}/templescan/res/mipmap-xhdpi/ic_launcher.png (100%) rename {multisrc/overrides/madara => src/en}/templescan/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {multisrc/overrides/madara => src/en}/templescan/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {multisrc/overrides/madara => src/en}/templescan/res/web_hi_res_512.png (100%) create mode 100644 src/en/templescan/src/eu/kanade/tachiyomi/extension/en/templescan/TempleScan.kt create mode 100644 src/en/templescan/src/eu/kanade/tachiyomi/extension/en/templescan/TempleScanUrlActivity.kt diff --git a/multisrc/overrides/madara/templescan/src/TempleScan.kt b/multisrc/overrides/madara/templescan/src/TempleScan.kt deleted file mode 100644 index a7537980a..000000000 --- a/multisrc/overrides/madara/templescan/src/TempleScan.kt +++ /dev/null @@ -1,47 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.templescan - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import okhttp3.OkHttpClient -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class TempleScan : Madara( - "Temple Scan", - "https://templescan.net", - "en", - SimpleDateFormat("dd.MM.yyyy", Locale.ENGLISH), -) { - override val mangaSubString = "comic" - override val useNewChapterEndpoint = true - override fun popularMangaSelector() = "div.c-tabs-item > div > div" - override val popularMangaUrlSelector = "div.series-box a" - override val mangaDetailsSelectorStatus = ".post-content_item:contains(Status) .summary-content" - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1) - .build() - - override fun popularMangaFromElement(element: Element): SManga { - return super.popularMangaFromElement(element).apply { - title = element.select(popularMangaUrlSelector).text() - } - } - - override fun searchPage(page: Int): String { - return if (page > 1) { - "page/$page/" - } else { - "" - } - } - - override fun chapterFromElement(element: Element): SChapter { - return super.chapterFromElement(element).apply { - name = element.select(".chapter-manhwa-title").text() - } - } -} 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 04406bb4d..b3e151d37 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 @@ -407,7 +407,6 @@ class MadaraGenerator : ThemeSourceGenerator { SingleLang("Taurus Fansub", "https://taurusfansub.com", "es"), SingleLang("Tecno Scan", "https://tecnoscann.com", "es"), SingleLang("TeenManhua", "https://teenmanhua.com", "en", overrideVersionCode = 1), - SingleLang("Temple Scan", "https://templescan.net", "en"), SingleLang("The Beginning After The End", "https://www.thebeginningaftertheend.fr", "fr", overrideVersionCode = 1), SingleLang("The Guild", "https://theguildscans.com", "en"), SingleLang("The Sugar", "https://thesugarscan.com", "pt-BR"), diff --git a/src/en/templescan/AndroidManifest.xml b/src/en/templescan/AndroidManifest.xml new file mode 100644 index 000000000..ee2d3d397 --- /dev/null +++ b/src/en/templescan/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + diff --git a/src/en/templescan/build.gradle b/src/en/templescan/build.gradle new file mode 100644 index 000000000..84d9cbb84 --- /dev/null +++ b/src/en/templescan/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'Temple Scan' + pkgNameSuffix = 'en.templescan' + extClass = '.TempleScan' + extVersionCode = 32 +} + +apply from: "$rootDir/common.gradle" diff --git a/multisrc/overrides/madara/templescan/res/mipmap-hdpi/ic_launcher.png b/src/en/templescan/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/templescan/res/mipmap-hdpi/ic_launcher.png rename to src/en/templescan/res/mipmap-hdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/templescan/res/mipmap-mdpi/ic_launcher.png b/src/en/templescan/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/templescan/res/mipmap-mdpi/ic_launcher.png rename to src/en/templescan/res/mipmap-mdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/templescan/res/mipmap-xhdpi/ic_launcher.png b/src/en/templescan/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/templescan/res/mipmap-xhdpi/ic_launcher.png rename to src/en/templescan/res/mipmap-xhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/templescan/res/mipmap-xxhdpi/ic_launcher.png b/src/en/templescan/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/templescan/res/mipmap-xxhdpi/ic_launcher.png rename to src/en/templescan/res/mipmap-xxhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/templescan/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/templescan/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/templescan/res/mipmap-xxxhdpi/ic_launcher.png rename to src/en/templescan/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/templescan/res/web_hi_res_512.png b/src/en/templescan/res/web_hi_res_512.png similarity index 100% rename from multisrc/overrides/madara/templescan/res/web_hi_res_512.png rename to src/en/templescan/res/web_hi_res_512.png diff --git a/src/en/templescan/src/eu/kanade/tachiyomi/extension/en/templescan/TempleScan.kt b/src/en/templescan/src/eu/kanade/tachiyomi/extension/en/templescan/TempleScan.kt new file mode 100644 index 000000000..60d08af61 --- /dev/null +++ b/src/en/templescan/src/eu/kanade/tachiyomi/extension/en/templescan/TempleScan.kt @@ -0,0 +1,199 @@ +package eu.kanade.tachiyomi.extension.en.templescan + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit +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.HttpSource +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.Response +import rx.Observable +import uy.kohesive.injekt.injectLazy +import java.util.Calendar +import kotlin.math.min + +class TempleScan : HttpSource() { + + override val name = "Temple Scan" + + override val lang = "en" + + override val baseUrl = "https://templescan.net" + + override val supportsLatest = false + + override val versionId = 2 + + override val client = network.cloudflareClient.newBuilder() + .rateLimit(1) + .build() + + private val json: Json by injectLazy() + + private val seriesCache: List by lazy { + val response = client.newCall(GET(baseUrl, headers)).execute() + + if (response.isSuccessful.not()) { + response.close() + throw Exception("Http Error ${response.code}") + } + + response.asJsoup() + .selectFirst("script:containsData(proyectos)") + ?.data() + ?.let { proyectosRegex.find(it)?.groupValues?.get(1) } + ?.let(json::decodeFromString) + ?: throw Exception(SeriesCacheFailureException) + } + + private lateinit var filteredSeriesCache: List + + private fun List.toMangasPage(page: Int): MangasPage { + val end = min(page * limit, this.size) + val entries = this.subList((page - 1) * limit, end) + .map(Series::toSManga) + + return MangasPage(entries, end < this.size) + } + + @Serializable + data class Series( + @SerialName("nombre") val name: String, + val slug: String, + @SerialName("portada") val cover: String, + ) { + fun toSManga() = SManga.create().apply { + url = "/comic/$slug" + title = name + thumbnail_url = cover + } + } + + override fun fetchPopularManga(page: Int): Observable { + val mangasPage = seriesCache.toMangasPage(page) + + return Observable.just(mangasPage) + } + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + if (query.startsWith(SEARCH_PREFIX)) { + val url = "/comic/${query.substringAfter(SEARCH_PREFIX)}" + val manga = SManga.create().apply { this.url = url } + return fetchMangaDetails(manga).map { + val newManga = it.apply { this.url = url } + MangasPage(listOf(newManga), false) + } + } + + if (page == 1) { + filteredSeriesCache = seriesCache.filter { + it.name.contains(query.trim(), true) + } + } + + val mangasPage = filteredSeriesCache.toMangasPage(page) + + return Observable.just(mangasPage) + } + + override fun mangaDetailsParse(response: Response): SManga { + val document = response.asJsoup() + + return SManga.create().apply { + thumbnail_url = document.select(".max-w-80 > img").attr("abs:src") + description = document.select("section[id=section-sinopsis] p").text() + title = document.select("h1").text() + genre = document.select("div.flex div:contains(gen) + div a").joinToString { it.text().trim() } + author = document.selectFirst("div.flex div:contains(aut) + div")?.text() + } + } + + override fun chapterListParse(response: Response): List { + val elements = response.asJsoup() + .select("div.contenedor-capitulo-miniatura a") + + return elements.map { element -> + SChapter.create().apply { + setUrlWithoutDomain(element.attr("href")) + name = element.select("div[id=name]").text() + date_upload = element.select("time").text().let { + runCatching { it.parseRelativeDate() }.getOrDefault(0L) + } + } + } + } + + override fun pageListParse(response: Response): List { + val elements = response.asJsoup() + .select("main div img") + + return elements.mapIndexed { index, element -> + Page(index, "", element.attr("abs:src")) + } + } + + private fun String.parseRelativeDate(): Long { + val now = Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + } + + var parsedDate = 0L + + val relativeDate = try { + this.split(" ")[0].trim().toInt() + } catch (e: NumberFormatException) { + return 0L + } + + when { + "second" in this -> { + parsedDate = now.apply { add(Calendar.SECOND, -relativeDate) }.timeInMillis + } + "minute" in this -> { + parsedDate = now.apply { add(Calendar.MINUTE, -relativeDate) }.timeInMillis + } + "hour" in this -> { + parsedDate = now.apply { add(Calendar.HOUR, -relativeDate) }.timeInMillis + } + "day" in this -> { + parsedDate = now.apply { add(Calendar.DAY_OF_YEAR, -relativeDate) }.timeInMillis + } + "week" in this -> { + parsedDate = now.apply { add(Calendar.WEEK_OF_YEAR, -relativeDate) }.timeInMillis + } + "month" in this -> { + parsedDate = now.apply { add(Calendar.MONTH, -relativeDate) }.timeInMillis + } + "year" in this -> { + parsedDate = now.apply { add(Calendar.YEAR, -relativeDate) }.timeInMillis + } + } + return parsedDate + } + + companion object { + private val proyectosRegex = Regex("""proyectos\s*=\s*([^\;]+)""") + private const val SeriesCacheFailureException = "Unable to extract series information" + + private const val limit = 20 + const val SEARCH_PREFIX = "slug:" + } + + override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException("Not Used") + override fun popularMangaParse(response: Response) = throw UnsupportedOperationException("Not Used") + override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("Not Used") + override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException("Not Used") + override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Not Used") + override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException("Not Used") + override fun searchMangaParse(response: Response) = throw UnsupportedOperationException("Not Used") +} diff --git a/src/en/templescan/src/eu/kanade/tachiyomi/extension/en/templescan/TempleScanUrlActivity.kt b/src/en/templescan/src/eu/kanade/tachiyomi/extension/en/templescan/TempleScanUrlActivity.kt new file mode 100644 index 000000000..df6c5a9e1 --- /dev/null +++ b/src/en/templescan/src/eu/kanade/tachiyomi/extension/en/templescan/TempleScanUrlActivity.kt @@ -0,0 +1,34 @@ +package eu.kanade.tachiyomi.extension.en.templescan + +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Intent +import android.os.Bundle +import android.util.Log +import kotlin.system.exitProcess + +class TempleScanUrlActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val pathSegments = intent?.data?.pathSegments + if (pathSegments != null && pathSegments.size > 1) { + val slug = pathSegments[1] + val mainIntent = Intent().apply { + action = "eu.kanade.tachiyomi.SEARCH" + putExtra("query", "${TempleScan.SEARCH_PREFIX}$slug") + putExtra("filter", packageName) + } + + try { + startActivity(mainIntent) + } catch (e: ActivityNotFoundException) { + Log.e("TempleScanUrlActivity", e.toString()) + } + } else { + Log.e("TempleScanUrlActivity", "could not parse uri from intent $intent") + } + + finish() + exitProcess(0) + } +}