diff --git a/src/en/templescan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/heancms/templescan/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from src/en/templescan/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/heancms/templescan/res/mipmap-hdpi/ic_launcher.png diff --git a/src/en/templescan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/heancms/templescan/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from src/en/templescan/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/heancms/templescan/res/mipmap-mdpi/ic_launcher.png diff --git a/src/en/templescan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/heancms/templescan/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from src/en/templescan/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/heancms/templescan/res/mipmap-xhdpi/ic_launcher.png diff --git a/src/en/templescan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/heancms/templescan/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from src/en/templescan/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/heancms/templescan/res/mipmap-xxhdpi/ic_launcher.png diff --git a/src/en/templescan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/heancms/templescan/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from src/en/templescan/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/heancms/templescan/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/src/en/templescan/res/web_hi_res_512.png b/multisrc/overrides/heancms/templescan/res/web_hi_res_512.png similarity index 100% rename from src/en/templescan/res/web_hi_res_512.png rename to multisrc/overrides/heancms/templescan/res/web_hi_res_512.png diff --git a/multisrc/overrides/heancms/templescan/src/TempleScan.kt b/multisrc/overrides/heancms/templescan/src/TempleScan.kt new file mode 100644 index 000000000..1470f122f --- /dev/null +++ b/multisrc/overrides/heancms/templescan/src/TempleScan.kt @@ -0,0 +1,35 @@ +package eu.kanade.tachiyomi.extension.en.templescan + +import eu.kanade.tachiyomi.multisrc.heancms.Genre +import eu.kanade.tachiyomi.multisrc.heancms.HeanCms +import eu.kanade.tachiyomi.network.interceptor.rateLimit + +class TempleScan : HeanCms("Temple Scan", "https://templescan.net", "en") { + + override val versionId = 3 + + override val client = super.client.newBuilder() + .rateLimit(1) + .build() + + override val useNewQueryEndpoint = true + override val coverPath = "" + override val mangaSubDirectory = "comic" + + override fun getGenreList() = listOf( + Genre("Drama", 1), + Genre("Josei", 2), + Genre("Romance", 3), + Genre("Girls Love", 4), + Genre("Reincarnation", 5), + Genre("Fantasia", 6), + Genre("Ecchi", 7), + Genre("Adventure", 8), + Genre("Boys Love", 9), + Genre("School Life", 10), + Genre("Action", 11), + Genre("Military", 13), + Genre("Comedy", 14), + Genre("Apocalypse", 15), + ) +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCms.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCms.kt index b6171892a..8299b9a41 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCms.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCms.kt @@ -63,6 +63,8 @@ abstract class HeanCms( protected open val coverPath: String = "cover/" + protected open val mangaSubDirectory: String = "series" + protected open val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ", Locale.US) override fun headersBuilder(): Headers.Builder = Headers.Builder() @@ -116,7 +118,7 @@ abstract class HeanCms( preferences.slugMap = preferences.slugMap.toMutableMap() .also { map -> map[it.slug.toPermSlugIfNeeded()] = it.slug } } - it.toSManga(apiUrl, coverPath, slugStrategy) + it.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy) } fetchAllTitles() @@ -130,7 +132,7 @@ abstract class HeanCms( preferences.slugMap = preferences.slugMap.toMutableMap() .also { map -> map[it.slug.toPermSlugIfNeeded()] = it.slug } } - it.toSManga(apiUrl, coverPath, slugStrategy) + it.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy) } fetchAllTitles() @@ -186,9 +188,9 @@ abstract class HeanCms( val manga = SManga.create().apply { url = if (slugStrategy != SlugStrategy.NONE) { val mangaId = getIdBySlug(slug) - "/series/${slug.toPermSlugIfNeeded()}#$mangaId" + "/$mangaSubDirectory/${slug.toPermSlugIfNeeded()}#$mangaId" } else { - "/series/$slug" + "/$mangaSubDirectory/$slug" } } @@ -285,7 +287,7 @@ abstract class HeanCms( .filter { it.type == "Comic" } .map { it.slug = it.slug.toPermSlugIfNeeded() - it.toSManga(apiUrl, coverPath, seriesSlugMap.orEmpty(), slugStrategy) + it.toSManga(apiUrl, coverPath, mangaSubDirectory, seriesSlugMap.orEmpty(), slugStrategy) } return MangasPage(mangaList, false) @@ -298,7 +300,7 @@ abstract class HeanCms( preferences.slugMap = preferences.slugMap.toMutableMap() .also { map -> map[it.slug.toPermSlugIfNeeded()] = it.slug } } - it.toSManga(apiUrl, coverPath, slugStrategy) + it.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy) } fetchAllTitles() @@ -312,7 +314,7 @@ abstract class HeanCms( preferences.slugMap = preferences.slugMap.toMutableMap() .also { map -> map[it.slug.toPermSlugIfNeeded()] = it.slug } } - it.toSManga(apiUrl, coverPath, slugStrategy) + it.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy) } fetchAllTitles() @@ -332,7 +334,7 @@ abstract class HeanCms( seriesSlug } - return "$baseUrl/series/$currentSlug" + return "$baseUrl/$mangaSubDirectory/$currentSlug" } override fun mangaDetailsRequest(manga: SManga): Request { @@ -380,7 +382,7 @@ abstract class HeanCms( .also { it[seriesResult.slug.toPermSlugIfNeeded()] = seriesResult.slug } } - val seriesDetails = seriesResult.toSManga(apiUrl, coverPath, slugStrategy) + val seriesDetails = seriesResult.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy) return seriesDetails.apply { status = status.takeUnless { it == SManga.UNKNOWN } @@ -404,13 +406,13 @@ abstract class HeanCms( return result.seasons.orEmpty() .flatMap { it.chapters.orEmpty() } .filterNot { it.price == 1 } - .map { it.toSChapter(result.slug, dateFormat, slugStrategy) } + .map { it.toSChapter(result.slug, mangaSubDirectory, dateFormat, slugStrategy) } .filter { it.date_upload <= currentTimestamp } } return result.chapters.orEmpty() .filterNot { it.price == 1 } - .map { it.toSChapter(result.slug, dateFormat, slugStrategy) } + .map { it.toSChapter(result.slug, mangaSubDirectory, dateFormat, slugStrategy) } .filter { it.date_upload <= currentTimestamp } .reversed() } @@ -419,7 +421,7 @@ abstract class HeanCms( if (slugStrategy == SlugStrategy.NONE) return baseUrl + chapter.url val seriesSlug = chapter.url - .substringAfter("/series/") + .substringAfter("/$mangaSubDirectory/") .substringBefore("/") .toPermSlugIfNeeded() @@ -432,7 +434,7 @@ abstract class HeanCms( override fun pageListRequest(chapter: SChapter): Request { if (useNewQueryEndpoint) { if (slugStrategy != SlugStrategy.NONE) { - val seriesPermSlug = chapter.url.substringAfter("/series/").substringBefore("/") + val seriesPermSlug = chapter.url.substringAfter("/$mangaSubDirectory/").substringBefore("/") val seriesSlug = preferences.slugMap[seriesPermSlug] ?: seriesPermSlug val chapterUrl = chapter.url.replaceFirst(seriesPermSlug, seriesSlug) return GET(baseUrl + chapterUrl, headers) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsDto.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsDto.kt index f9c2578c4..50d045caa 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsDto.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsDto.kt @@ -36,6 +36,7 @@ data class HeanCmsSearchDto( fun toSManga( apiUrl: String, coverPath: String, + mangaSubDirectory: String, slugMap: Map, slugStrategy: SlugStrategy, ): SManga = SManga.create().apply { @@ -44,7 +45,7 @@ data class HeanCmsSearchDto( title = this@HeanCmsSearchDto.title thumbnail_url = thumbnail?.toAbsoluteThumbnailUrl(apiUrl, coverPath) ?: thumbnailFileName?.toAbsoluteThumbnailUrl(apiUrl, coverPath) - url = "/series/$slugOnly" + url = "/$mangaSubDirectory/$slugOnly" } } @@ -67,6 +68,7 @@ data class HeanCmsSeriesDto( fun toSManga( apiUrl: String, coverPath: String, + mangaSubDirectory: String, slugStrategy: SlugStrategy, ): SManga = SManga.create().apply { val descriptionBody = this@HeanCmsSeriesDto.description?.let(Jsoup::parseBodyFragment) @@ -85,9 +87,9 @@ data class HeanCmsSeriesDto( ?.toAbsoluteThumbnailUrl(apiUrl, coverPath) status = this@HeanCmsSeriesDto.status?.toStatus() ?: SManga.UNKNOWN url = if (slugStrategy != SlugStrategy.NONE) { - "/series/$slugOnly#$id" + "/$mangaSubDirectory/$slugOnly#$id" } else { - "/series/$slug" + "/$mangaSubDirectory/$slug" } } } @@ -112,6 +114,7 @@ data class HeanCmsChapterDto( ) { fun toSChapter( seriesSlug: String, + mangaSubDirectory: String, dateFormat: SimpleDateFormat, slugStrategy: SlugStrategy, ): SChapter = SChapter.create().apply { @@ -119,7 +122,7 @@ data class HeanCmsChapterDto( name = this@HeanCmsChapterDto.name.trim() date_upload = runCatching { dateFormat.parse(createdAt)?.time } .getOrNull() ?: 0L - url = "/series/$seriesSlugOnly/$slug#$id" + url = "/$mangaSubDirectory/$seriesSlugOnly/$slug#$id" } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsGenerator.kt index 7e3b0b53c..834fab332 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsGenerator.kt @@ -16,6 +16,7 @@ class HeanCmsGenerator : ThemeSourceGenerator { SingleLang("Omega Scans", "https://omegascans.org", "en", isNsfw = true, overrideVersionCode = 18), SingleLang("Perf Scan", "https://perf-scan.fr", "fr"), SingleLang("Reaper Scans", "https://reaperscans.net", "pt-BR", overrideVersionCode = 36), + SingleLang("Temple Scan", "https://templescan.net", "en", isNsfw = true, overrideVersionCode = 16), SingleLang("YugenMangas", "https://yugenmangas.net", "es", isNsfw = true, overrideVersionCode = 9), ) diff --git a/src/en/templescan/AndroidManifest.xml b/src/en/templescan/AndroidManifest.xml deleted file mode 100644 index 15f9ec69d..000000000 --- a/src/en/templescan/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/en/templescan/build.gradle b/src/en/templescan/build.gradle deleted file mode 100644 index 303636911..000000000 --- a/src/en/templescan/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' - -ext { - extName = 'Temple Scan' - pkgNameSuffix = 'en.templescan' - extClass = '.TempleScan' - extVersionCode = 33 -} - -apply from: "$rootDir/common.gradle" 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 deleted file mode 100644 index 5e82ea62c..000000000 --- a/src/en/templescan/src/eu/kanade/tachiyomi/extension/en/templescan/TempleScan.kt +++ /dev/null @@ -1,217 +0,0 @@ -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 org.jsoup.nodes.Document -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 = true - - override val versionId = 2 - - override val client = network.cloudflareClient.newBuilder() - .rateLimit(1) - .build() - - private val json: Json by injectLazy() - - private val homeDocument: Document by lazy { - val response = client.newCall(GET(baseUrl, headers)).execute() - - if (response.isSuccessful.not()) { - response.close() - throw Exception("Http Error ${response.code}") - } - - response.use { it.asJsoup() } - } - - private val seriesCache: List by lazy { - homeDocument.selectFirst("script:containsData(proyectos)") - ?.data() - ?.let { proyectosRegex.find(it)?.groupValues?.get(1) } - ?.let(json::decodeFromString) - ?: throw Exception("Unable to extract series information") - } - - 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 fetchLatestUpdates(page: Int): Observable { - val slugs = homeDocument.select("section:contains(new release) figure") - .mapNotNull { element -> - element.selectFirst("a")?.attr("abs:href") - ?.substringAfter("/comic/") - ?.substringBefore("/") - } - - val entries = slugs.mapNotNull { slug -> - seriesCache.firstOrNull { it.slug == slug }?.toSManga() - } - - val mangasPage = MangasPage(entries, false) - - 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") - - return elements.map { element -> - SChapter.create().apply { - setUrlWithoutDomain(element.select("a").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 = this.split(" ")[0].trim().toInt() - - 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 by lazy { - Regex("""proyectos\s*=\s*([^;]+)""") - } - - 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 deleted file mode 100644 index df6c5a9e1..000000000 --- a/src/en/templescan/src/eu/kanade/tachiyomi/extension/en/templescan/TempleScanUrlActivity.kt +++ /dev/null @@ -1,34 +0,0 @@ -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) - } -}