SpectralScan: migrate to new site (#9360)

Migration
This commit is contained in:
Chopper 2025-06-21 07:55:48 -03:00 committed by Draff
parent 9995c4be38
commit 50eac7a152
Signed by: Draff
GPG Key ID: E8A89F3211677653
3 changed files with 193 additions and 11 deletions

View File

@ -1,9 +1,7 @@
ext {
extName = 'Spectral Scan'
extClass = '.SpectralScan'
themePkg = 'yuyu'
baseUrl = 'https://spectralscan.xyz'
overrideVersionCode = 41
extVersionCode = 43
isNsfw = false
}

View File

@ -0,0 +1,62 @@
package eu.kanade.tachiyomi.extension.pt.spectralscan
import eu.kanade.tachiyomi.source.model.Filter
class SelectFilter(displayName: String = "", val parameter: String = "", private val vals: Array<Pair<String, String>>, state: Int = 0) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), state) {
fun selected() = vals[state].second
}
val sortList = arrayOf(
"Mais Recentes" to "latest",
"Mais Populares" to "popular",
"Melhor Avaliação" to "rating",
"Nome (A-Z)" to "name_asc",
"Nome (Z-A)" to "name_desc",
)
val genreList = arrayOf(
"Todos os Gêneros" to "",
"Ação" to "acao",
"Armas e Combate" to "armas-e-combate",
"Artes Marciais" to "artes-marciais",
"Aventura" to "aventura",
"Comédia" to "comedia",
"Comida" to "comida",
"Cultivo" to "cultivo",
"Cyberpunk" to "cyberpunk",
"Drama" to "drama",
"Ecchi" to "ecchi",
"Escolar" to "escolar",
"Espacial" to "espacial",
"Esportes" to "esportes",
"Fantasia" to "fantasia",
"Ficção Científica" to "ficcao-cientifica",
"Harem" to "harem",
"Hentai" to "hentai",
"Horror" to "horror",
"Jogo" to "jogo",
"Josei" to "josei",
"Kodomomuke" to "kodomomuke",
"Mature Themes" to "mature-themes",
"Mecha" to "mecha",
"Mistério" to "misterio",
"Monstros" to "monstros",
"Psicológico" to "psicologico",
"Realidade Virtual" to "realidade-virtual",
"Reencarnação" to "reencarnacao",
"Regressão" to "regressao",
"Romance" to "romance",
"Seinen" to "seinen",
"Shoujo" to "shoujo",
"Shounen" to "shounen",
"Sistema" to "sistema",
"Slice of Life" to "slice-of-life",
"Sobrenatural" to "sobrenatural",
"Superpoderes" to "superpoderes",
"Suspense" to "suspense",
"Tela de Sistema" to "tela-de-sistema",
"Tragédia" to "tragedia",
"Vida Cotidiana" to "vida-cotidiana",
"Volta no Tempo" to "volta-no-tempo",
)

View File

@ -1,17 +1,139 @@
package eu.kanade.tachiyomi.extension.pt.spectralscan
import eu.kanade.tachiyomi.multisrc.yuyu.YuYu
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.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 okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
class SpectralScan : ParsedHttpSource() {
override val lang = "pt-BR"
override val name = "Spectral Scan"
override val baseUrl = "https://spectralscan.xyz"
override val supportsLatest = true
override val versionId = 3
class SpectralScan : YuYu(
"Spectral Scan",
"https://spectralscan.xyz",
"pt-BR",
) {
override val client = super.client.newBuilder()
.rateLimit(2)
.build()
// Moved from Madara to YuYu
override val versionId = 2
// ==================== Popular ==========================
private val popularFilter = FilterList(SelectFilter(vals = arrayOf("" to "popular"), parameter = "sort"))
override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", popularFilter)
override fun popularMangaSelector() = searchMangaSelector()
override fun popularMangaFromElement(element: Element) = searchMangaFromElement(element)
override fun popularMangaNextPageSelector() = searchMangaNextPageSelector()
// ==================== Latest ==========================
private val latestFilter = FilterList(SelectFilter(vals = arrayOf("" to "latest"), parameter = "sort"))
override fun latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", latestFilter)
override fun latestUpdatesSelector() = searchMangaSelector()
override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element)
override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector()
// ==================== Search ==========================
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/mangas".toHttpUrl().newBuilder()
.addQueryParameter("page", page.toString())
.addQueryParameter("q", query)
filters.forEach { filter ->
when (filter) {
is SelectFilter -> {
url.addQueryParameter(filter.parameter, filter.selected())
}
else -> {}
}
}
return GET(url.build(), headers)
}
override fun searchMangaSelector() = ".content-grid .content-card"
override fun searchMangaFromElement(element: Element) = SManga.create().apply {
title = element.selectFirst(".p-3 .text-sm")!!.text()
thumbnail_url = element.selectFirst("img")?.absUrl("src")
setUrlWithoutDomain(element.absUrl("href"))
}
override fun searchMangaNextPageSelector() = "a[title='Última Página']"
// ==================== Details =======================
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.selectFirst("h1")!!.text()
thumbnail_url = document.selectFirst("img.item-cover-image")?.absUrl("src")
description = document.selectFirst(".item-description")?.text()
genre = document.select(".item-genres a").joinToString { it.text() }
document.select("span:contains(Status) + span")?.text()?.let {
status = when (it.lowercase()) {
"em andamento" -> SManga.ONGOING
else -> SManga.UNKNOWN
}
}
setUrlWithoutDomain(document.location())
}
// ==================== Chapter =======================
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
val element = document.selectFirst("section.chapter-section")!!
val chapters = element.attr("data-total-chapters")
val pathSegment = element.attr("data-ajax-url")
val url = "$baseUrl$pathSegment".toHttpUrl().newBuilder()
.addQueryParameter("per_page", chapters)
.build()
val newHeaders = headers.newBuilder()
.set("X-Requested-With", "XMLHttpRequest")
.build()
return super.chapterListParse(client.newCall(GET(url, newHeaders)).execute())
}
override fun chapterListSelector() = ".chapter-item"
override fun chapterFromElement(element: Element) = SChapter.create().apply {
name = element.selectFirst(".chapter-number")!!.text()
scanlator = element.selectFirst(".chapter-meta span")?.text()
setUrlWithoutDomain(element.absUrl("data-final-url"))
}
// ==================== Page ==========================
override fun pageListParse(document: Document): List<Page> {
return document.select("#chapterPagesContainer img").mapIndexed { index, element ->
Page(index, imageUrl = element.absUrl("src"))
}
}
override fun imageUrlParse(document: Document) = ""
// ==================== Filters ==========================
override fun getFilterList(): FilterList {
return FilterList(
SelectFilter("Ordenar Por", "sort", sortList),
SelectFilter("Gênero", "genre", genreList),
)
}
}