Fix Philiascans (#10322)

This commit is contained in:
manti 2025-09-01 11:06:55 +02:00 committed by Draff
parent 79bdda34b2
commit efe09f539b
Signed by: Draff
GPG Key ID: E8A89F3211677653
2 changed files with 216 additions and 23 deletions

View File

@ -1,9 +1,7 @@
ext { ext {
extName = 'Philia Scans' extName = 'Philia Scans'
extClass = '.PhiliaScans' extClass = '.PhiliaScans'
themePkg = 'madara' extVersionCode = 44
baseUrl = "https://philiascans.org"
overrideVersionCode = 1
isNsfw = false isNsfw = false
} }

View File

@ -1,44 +1,239 @@
package eu.kanade.tachiyomi.extension.en.philiascans package eu.kanade.tachiyomi.extension.en.philiascans
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
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.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import java.text.SimpleDateFormat import okhttp3.Response
import java.util.Locale import org.jsoup.nodes.Element
class PhiliaScans : Madara( class PhiliaScans : HttpSource() {
"Philia Scans",
"https://philiascans.org",
"en",
SimpleDateFormat("dd/MMM", Locale.US),
) {
override val versionId: Int = 2
override val useNewChapterEndpoint = true
override fun popularMangaSelector(): String = ".manga__item" override val name = "Philia Scans"
override fun latestUpdatesSelector(): String = popularMangaSelector() override val baseUrl = "https://philiascans.org"
override fun searchMangaSelector(): String = popularMangaSelector() override val lang = "en"
override val supportsLatest = true
override val versionId: Int = 3
override val mangaDetailsSelectorTitle: String = "h1.post-title" override val client: OkHttpClient = network.cloudflareClient
override val mangaDetailsSelectorStatus: String = "div.summary-heading:contains(Status) + div"
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
val url = baseUrl.toHttpUrl().newBuilder().apply { val url = baseUrl.toHttpUrl().newBuilder().apply {
addQueryParameter("post_type", "wp-manga") addQueryParameter("post_type", "wp-manga")
addQueryParameter("m_orderby", "views") addQueryParameter("s", "")
addQueryParameter("sort", "most_viewed")
addQueryParameter("paged", page.toString())
}.build() }.build()
return GET(url, headers) return GET(url, headers)
} }
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
val mangas = document.select("div.unit").map { popularMangaFromElement(it) }
val hasNextPage = document.selectFirst("li.page-item:not(.disabled) a.page-link[rel=next]") != null
return MangasPage(mangas, hasNextPage)
}
private fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
val titleLink = element.selectFirst("a.c-title")!!
title = titleLink.text()
setUrlWithoutDomain(titleLink.attr("href"))
thumbnail_url = element.selectFirst("a.poster div.poster-image-wrapper > img")?.attr("abs:src")
}
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/recently-updated/?page=$page", headers)
}
override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = baseUrl.toHttpUrl().newBuilder().apply { val url = baseUrl.toHttpUrl().newBuilder().apply {
addQueryParameter("post_type", "wp-manga") addQueryParameter("post_type", "wp-manga")
addQueryParameter("m_orderby", "latest") addQueryParameter("s", query)
addQueryParameter("paged", page.toString())
filters.forEach { filter ->
when (filter) {
is SortBy -> {
val sort = filter.toUriPart()
if (sort.isNotEmpty()) addQueryParameter("sort", sort)
}
is TypeList ->
filter.state
.filter { it.state }
.forEach { addQueryParameter("type[]", it.value) }
is YearList ->
filter.state
.filter { it.state }
.forEach { addQueryParameter("release[]", it.name) }
is GenreList ->
filter.state
.filter { it.state }
.forEach { addQueryParameter("genre[]", it.id) }
is GenreInclusion -> {
if (filter.state) addQueryParameter("genre_mode", "and")
}
else -> {}
}
}
}.build() }.build()
return GET(url, headers) return GET(url, headers)
} }
// needed to exclude paid chapters override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
override fun chapterListSelector(): String = """li.wp-manga-chapter:not(:has(a[href="#"]))"""
override fun mangaDetailsParse(response: Response): SManga {
val document = response.asJsoup()
return SManga.create().apply {
title = document.selectFirst("h1.serie-title")!!.text()
author = document.selectFirst(".stat-item:has(.stat-label:contains(Author)) .stat-value")?.text()
artist = document.selectFirst(".stat-item:has(.stat-label:contains(Artist)) .stat-value")?.text()
genre = document.select("div.genre-list a").joinToString { it.text() }
status = parseStatus(document.selectFirst(".stat-item:has(.stat-label:contains(Status)) span:not(.stat-label)")?.text())
description = document.selectFirst("div.description-content")?.text()
thumbnail_url = imageFromElement(document.selectFirst("div.main-cover img.cover"))
}
}
private fun parseStatus(status: String?): Int = when {
status == null -> SManga.UNKNOWN
status.contains("Releasing", true) -> SManga.ONGOING
status.contains("Completed", true) -> SManga.COMPLETED
status.contains("On Hold", true) -> SManga.ON_HIATUS
status.contains("Canceled", true) -> SManga.CANCELLED
else -> SManga.UNKNOWN
}
override fun chapterListParse(response: Response): List<SChapter> {
return response.asJsoup().select("li.item")
.toList()
.filter { it.selectFirst("a")?.attr("href")?.contains("#") == false } // Filter Coin Chapters
.map { chapterFromElement(it) }
}
private fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
val urlElement = element.selectFirst("a")!!
setUrlWithoutDomain(urlElement.attr("href"))
name = element.selectFirst("zebi")!!.text().trim()
}
override fun pageListParse(response: Response): List<Page> {
return response.asJsoup().select("div#ch-images img")
.mapIndexed { index, element ->
Page(index, imageUrl = imageFromElement(element))
}
}
private fun imageFromElement(element: Element?): String? {
return when {
element == null -> null
element.hasAttr("data-lazy-src") -> element.attr("abs:data-lazy-src")
element.hasAttr("data-src") -> element.attr("abs:data-src")
else -> element.attr("abs:src")
}
}
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
override fun getFilterList(): FilterList {
return FilterList(
Filter.Header("NOTE: Filters are applied when you search."),
SortBy(),
TypeList(getTypeList()),
YearList(getYearList()),
Filter.Separator(),
GenreList(getGenreList()),
GenreInclusion(),
)
}
private class SortBy : UriPartFilter(
"Sort by",
arrayOf(
Pair("Default", ""),
Pair("Newest", "recently_added"),
Pair("Alphabetical", "title_az"),
Pair("Most Viewed", "most_viewed"),
),
)
private class Type(name: String, val value: String) : Filter.CheckBox(name)
private class TypeList(types: List<Type>) : Filter.Group<Type>("Type", types)
private fun getTypeList() = listOf(
Type("Manga", "manga"),
Type("Manhua", "manhua"),
Type("Manhwa", "manhwa"),
Type("Seinen", "seinen"),
)
private class Year(name: String) : Filter.CheckBox(name)
private class YearList(years: List<Year>) : Filter.Group<Year>("Year", years)
private fun getYearList() = listOf(
Year("2025"),
Year("2024"),
Year("2023"),
Year("2022"),
Year("2021"),
Year("2020"),
Year("2019"),
)
private class Genre(name: String, val id: String) : Filter.CheckBox(name)
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
private class GenreInclusion : Filter.CheckBox("Must include all selected genres")
private fun getGenreList() = listOf(
Genre("Action", "29"),
Genre("Adventure", "38"),
Genre("Comedy", "42"),
Genre("Crime", "30"),
Genre("Drama", "34"),
Genre("Ecchi", "39"),
Genre("Fantasy", "43"),
Genre("Gore", "157"),
Genre("Gourmet", "188"),
Genre("Harem", "46"),
Genre("Historical", "40"),
Genre("Horror", "44"),
Genre("Isekai", "31"),
Genre("Josei", "173"),
Genre("Josei-None", "302"),
Genre("Josei-Seinen", "174"),
Genre("Magic", "87"),
Genre("Martial Arts", "61"),
Genre("Medical", "32"),
Genre("Monsters", "99"),
Genre("Music", "303"),
Genre("Mystery", "35"),
Genre("Psychological", "62"),
Genre("Regression", "122"),
Genre("Romance", "33"),
Genre("School Life", "47"),
Genre("Sci-Fi", "36"),
Genre("Seinen", "48"),
Genre("Shoujo", "69"),
Genre("Shounen", "55"),
Genre("Slice of Life", "37"),
Genre("Sports", "45"),
Genre("Supernatural", "49"),
Genre("Survival", "121"),
Genre("Tragedy", "41"),
Genre("Villainess", "253"),
Genre("War", "120"),
Genre("Yuri", "195"),
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
} }