Temple Scan: migrate to individual extension (#17294)
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"),
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="eu.kanade.tachiyomi.extension">
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:name=".en.templescan.TempleScanUrlActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="templescan.net"
|
||||
android:pathPattern="/comic/..*"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
|
@ -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"
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
@ -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<Series> 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<Series>
|
||||
|
||||
private fun List<Series>.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<MangasPage> {
|
||||
val mangasPage = seriesCache.toMangasPage(page)
|
||||
|
||||
return Observable.just(mangasPage)
|
||||
}
|
||||
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
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<SChapter> {
|
||||
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<Page> {
|
||||
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")
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|