This commit is contained in:
AwkwardPeak7 2025-02-02 13:35:52 +05:00 committed by Draff
parent e2c7543d3e
commit 2e45708568
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
11 changed files with 258 additions and 323 deletions

View File

@ -109,7 +109,9 @@ abstract class Madara(
protected open val useLoadMoreRequest = LoadMoreStrategy.AutoDetect
enum class LoadMoreStrategy {
AutoDetect, Always, Never
AutoDetect,
Always,
Never,
}
/**
@ -118,7 +120,9 @@ abstract class Madara(
private var loadMoreRequestDetected = LoadMoreDetection.Pending
private enum class LoadMoreDetection {
Pending, True, False
Pending,
True,
False,
}
protected fun detectLoadMore(document: Document) {
@ -132,12 +136,10 @@ abstract class Madara(
}
}
protected fun useLoadMoreRequest(): Boolean {
return when (useLoadMoreRequest) {
LoadMoreStrategy.Always -> true
LoadMoreStrategy.Never -> false
else -> loadMoreRequestDetected == LoadMoreDetection.True
}
protected fun useLoadMoreRequest(): Boolean = when (useLoadMoreRequest) {
LoadMoreStrategy.Always -> true
LoadMoreStrategy.Never -> false
else -> loadMoreRequestDetected == LoadMoreDetection.True
}
// Popular Manga
@ -176,19 +178,17 @@ abstract class Madara(
return manga
}
override fun popularMangaRequest(page: Int): Request =
if (useLoadMoreRequest()) {
loadMoreRequest(page, popular = true)
} else {
GET("$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=views", headers)
}
override fun popularMangaRequest(page: Int): Request = if (useLoadMoreRequest()) {
loadMoreRequest(page, popular = true)
} else {
GET("$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=views", headers)
}
override fun popularMangaNextPageSelector(): String? =
if (useLoadMoreRequest()) {
"body:not(:has(.no-posts))"
} else {
"div.nav-previous, nav.navigation-ajax, a.nextpostslink"
}
override fun popularMangaNextPageSelector(): String? = if (useLoadMoreRequest()) {
"body:not(:has(.no-posts))"
} else {
"div.nav-previous, nav.navigation-ajax, a.nextpostslink"
}
// Latest Updates
@ -199,12 +199,11 @@ abstract class Madara(
return popularMangaFromElement(element)
}
override fun latestUpdatesRequest(page: Int): Request =
if (useLoadMoreRequest()) {
loadMoreRequest(page, popular = false)
} else {
GET("$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=latest", headers)
}
override fun latestUpdatesRequest(page: Int): Request = if (useLoadMoreRequest()) {
loadMoreRequest(page, popular = false)
} else {
GET("$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=latest", headers)
}
override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector()
@ -251,20 +250,16 @@ abstract class Madara(
return super.fetchSearchManga(page, query, filters)
}
protected open fun searchPage(page: Int): String {
return if (page == 1) {
""
} else {
"page/$page/"
}
protected open fun searchPage(page: Int): String = if (page == 1) {
""
} else {
"page/$page/"
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return if (useLoadMoreRequest()) {
searchLoadMoreRequest(page, query, filters)
} else {
searchRequest(page, query, filters)
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = if (useLoadMoreRequest()) {
searchLoadMoreRequest(page, query, filters)
} else {
searchRequest(page, query, filters)
}
protected open fun searchRequest(page: Int, query: String, filters: FilterList): Request {
@ -310,7 +305,9 @@ abstract class Madara(
filter.state
.filter { it.state }
.let { list ->
if (list.isNotEmpty()) { list.forEach { genre -> url.addQueryParameter("genre[]", genre.id) } }
if (list.isNotEmpty()) {
list.forEach { genre -> url.addQueryParameter("genre[]", genre.id) }
}
}
}
else -> {}
@ -489,8 +486,7 @@ abstract class Madara(
intl["adult_content_filter_only"] to "1",
)
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>, state: Int = 0) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), state) {
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>, state: Int = 0) : Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), state) {
fun toUriPart() = vals[state].second
}
@ -499,21 +495,21 @@ abstract class Madara(
protected class AuthorFilter(title: String) : Filter.Text(title)
protected class ArtistFilter(title: String) : Filter.Text(title)
protected class YearFilter(title: String) : Filter.Text(title)
protected class StatusFilter(title: String, status: List<Tag>) :
Filter.Group<Tag>(title, status)
protected class StatusFilter(title: String, status: List<Tag>) : Filter.Group<Tag>(title, status)
protected class OrderByFilter(title: String, options: List<Pair<String, String>>, state: Int = 0) :
UriPartFilter(title, options.toTypedArray(), state)
protected class OrderByFilter(title: String, options: List<Pair<String, String>>, state: Int = 0) : UriPartFilter(title, options.toTypedArray(), state)
protected class GenreConditionFilter(title: String, options: List<Pair<String, String>>) : UriPartFilter(
title,
options.toTypedArray(),
)
protected class GenreConditionFilter(title: String, options: List<Pair<String, String>>) :
UriPartFilter(
title,
options.toTypedArray(),
)
protected class AdultContentFilter(title: String, options: List<Pair<String, String>>) : UriPartFilter(
title,
options.toTypedArray(),
)
protected class AdultContentFilter(title: String, options: List<Pair<String, String>>) :
UriPartFilter(
title,
options.toTypedArray(),
)
protected class GenreList(title: String, genres: List<Genre>) : Filter.Group<GenreCheckBox>(title, genres.map { GenreCheckBox(it.name, it.id) })
class GenreCheckBox(name: String, val id: String = name) : Filter.CheckBox(name)
@ -757,32 +753,24 @@ abstract class Madara(
open val altName = intl["alt_names_heading"]
open val updatingRegex = "Updating|Atualizando".toRegex(RegexOption.IGNORE_CASE)
fun String.notUpdating(): Boolean {
return this.contains(updatingRegex).not()
}
fun String.notUpdating(): Boolean = this.contains(updatingRegex).not()
private fun String.containsIn(array: Array<String>): Boolean {
return this.lowercase() in array.map { it.lowercase() }
}
private fun String.containsIn(array: Array<String>): Boolean = this.lowercase() in array.map { it.lowercase() }
protected open fun imageFromElement(element: Element): String? {
return when {
element.hasAttr("data-src") -> element.attr("abs:data-src")
element.hasAttr("data-lazy-src") -> element.attr("abs:data-lazy-src")
element.hasAttr("srcset") -> element.attr("abs:srcset").getSrcSetImage()
element.hasAttr("data-cfsrc") -> element.attr("abs:data-cfsrc")
else -> element.attr("abs:src")
}
protected open fun imageFromElement(element: Element): String? = when {
element.hasAttr("data-src") -> element.attr("abs:data-src")
element.hasAttr("data-lazy-src") -> element.attr("abs:data-lazy-src")
element.hasAttr("srcset") -> element.attr("abs:srcset").getSrcSetImage()
element.hasAttr("data-cfsrc") -> element.attr("abs:data-cfsrc")
else -> element.attr("abs:src")
}
/**
* Get the best image quality available from srcset
*/
protected fun String.getSrcSetImage(): String? {
return this.split(" ")
.filter(URL_REGEX::matches)
.maxOfOrNull(String::toString)
}
protected fun String.getSrcSetImage(): String? = this.split(" ")
.filter(URL_REGEX::matches)
.maxOfOrNull(String::toString)
/**
* Set it to true if the source uses the new AJAX endpoint to
@ -807,9 +795,7 @@ abstract class Madara(
return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, form)
}
protected open fun xhrChaptersRequest(mangaUrl: String): Request {
return POST("$mangaUrl/ajax/chapters", xhrHeaders)
}
protected open fun xhrChaptersRequest(mangaUrl: String): Request = POST("$mangaUrl/ajax/chapters", xhrHeaders)
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
@ -880,12 +866,10 @@ abstract class Madara(
open fun parseChapterDate(date: String?): Long {
date ?: return 0
fun SimpleDateFormat.tryParse(string: String): Long {
return try {
parse(string)?.time ?: 0
} catch (_: ParseException) {
0
}
fun SimpleDateFormat.tryParse(string: String): Long = try {
parse(string)?.time ?: 0
} catch (_: ParseException) {
0
}
return when {
@ -1006,9 +990,7 @@ abstract class Madara(
}
}
override fun imageRequest(page: Page): Request {
return GET(page.imageUrl!!, headers.newBuilder().set("Referer", page.url).build())
}
override fun imageRequest(page: Page): Request = GET(page.imageUrl!!, headers.newBuilder().set("Referer", page.url).build())
override fun imageUrlParse(document: Document) = ""
@ -1083,26 +1065,22 @@ abstract class Madara(
/**
* The request to the search page (or another one) that have the genres list.
*/
protected open fun genresRequest(): Request {
return GET("$baseUrl/?s=genre&post_type=wp-manga", headers)
}
protected open fun genresRequest(): Request = GET("$baseUrl/?s=genre&post_type=wp-manga", headers)
/**
* Get the genres from the search page document.
*
* @param document The search page document
*/
protected open fun parseGenres(document: Document): List<Genre> {
return document.selectFirst("div.checkbox-group")
?.select("div.checkbox")
.orEmpty()
.map { li ->
Genre(
li.selectFirst("label")!!.text(),
li.selectFirst("input[type=checkbox]")!!.`val`(),
)
}
}
protected open fun parseGenres(document: Document): List<Genre> = document.selectFirst("div.checkbox-group")
?.select("div.checkbox")
.orEmpty()
.map { li ->
Genre(
li.selectFirst("label")!!.text(),
li.selectFirst("input[type=checkbox]")!!.`val`(),
)
}
// https://stackoverflow.com/a/66614516
protected fun String.decodeHex(): ByteArray {

View File

@ -6,13 +6,11 @@ import org.jsoup.nodes.Element
class CoffeeManga : Madara("Coffee Manga", "https://coffeemanga.io", "en") {
override val useNewChapterEndpoint = false
override fun imageFromElement(element: Element): String? {
return when {
element.attr("data-src").isNotBlank() -> element.attr("abs:data-src")
element.attr("data-lazy-src").isNotBlank() -> element.attr("abs:data-lazy-src")
element.attr("srcset").isNotBlank() -> element.attr("abs:srcset").getSrcSetImage()
element.attr("data-cfsrc").isNotBlank() -> element.attr("abs:data-cfsrc")
else -> element.attr("abs:src")
}
override fun imageFromElement(element: Element): String? = when {
element.attr("data-src").isNotBlank() -> element.attr("abs:data-src")
element.attr("data-lazy-src").isNotBlank() -> element.attr("abs:data-lazy-src")
element.attr("srcset").isNotBlank() -> element.attr("abs:srcset").getSrcSetImage()
element.attr("data-cfsrc").isNotBlank() -> element.attr("abs:data-cfsrc")
else -> element.attr("abs:src")
}
}

View File

@ -2,11 +2,12 @@ package eu.kanade.tachiyomi.extension.en.luascans
import eu.kanade.tachiyomi.multisrc.heancms.HeanCms
class LuaScans : HeanCms(
"Lua Scans",
"https://luacomic.org",
"en",
) {
class LuaScans :
HeanCms(
"Lua Scans",
"https://luacomic.org",
"en",
) {
// Moved from Keyoapp to HeanCms
override val versionId = 3

View File

@ -2,11 +2,12 @@ package eu.kanade.tachiyomi.extension.en.retsu
import eu.kanade.tachiyomi.multisrc.madara.Madara
class Retsu : Madara(
"Retsu",
"https://retsu.org",
"en",
) {
class Retsu :
Madara(
"Retsu",
"https://retsu.org",
"en",
) {
override fun popularMangaSelector() = "div.manga__item"
override val popularMangaUrlSelector = "h4 a"

View File

@ -12,12 +12,13 @@ import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Locale
class SirenKomik : MangaThemesia(
"Siren Komik",
"https://sirenkomik.my.id",
"id",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("id")),
) {
class SirenKomik :
MangaThemesia(
"Siren Komik",
"https://sirenkomik.my.id",
"id",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("id")),
) {
override val id = 8457447675410081142
override val hasProjectPage = true

View File

@ -32,7 +32,9 @@ import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
class SussyToons : HttpSource(), ConfigurableSource {
class SussyToons :
HttpSource(),
ConfigurableSource {
override val name = "Sussy Toons"
@ -102,9 +104,7 @@ class SussyToons : HttpSource(), ConfigurableSource {
// ============================= Popular ==================================
override fun popularMangaRequest(page: Int): Request {
return GET("$apiUrl/obras/top5", headers)
}
override fun popularMangaRequest(page: Int): Request = GET("$apiUrl/obras/top5", headers)
override fun popularMangaParse(response: Response): MangasPage {
val dto = response.parseAs<WrapperDto<List<MangaDto>>>()
@ -152,8 +152,7 @@ class SussyToons : HttpSource(), ConfigurableSource {
return GET(url, headers)
}
override fun mangaDetailsParse(response: Response) =
response.parseAs<WrapperDto<MangaDto>>().results.toSManga()
override fun mangaDetailsParse(response: Response) = response.parseAs<WrapperDto<MangaDto>>().results.toSManga()
private val SManga.id: String get() {
val mangaUrl = apiUrl.toHttpUrl().newBuilder()
@ -166,18 +165,16 @@ class SussyToons : HttpSource(), ConfigurableSource {
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> {
return response.parseAs<WrapperDto<WrapperChapterDto>>().results.chapters.map {
SChapter.create().apply {
name = it.name
it.chapterNumber?.let {
chapter_number = it
}
setUrlWithoutDomain("$baseUrl/capitulo/${it.id}")
date_upload = it.updateAt.toDate()
override fun chapterListParse(response: Response): List<SChapter> = response.parseAs<WrapperDto<WrapperChapterDto>>().results.chapters.map {
SChapter.create().apply {
name = it.name
it.chapterNumber?.let {
chapter_number = it
}
}.sortedBy(SChapter::chapter_number).reversed()
}
setUrlWithoutDomain("$baseUrl/capitulo/${it.id}")
date_upload = it.updateAt.toDate()
}
}.sortedBy(SChapter::chapter_number).reversed()
// ============================= Pages ====================================
@ -208,30 +205,22 @@ class SussyToons : HttpSource(), ConfigurableSource {
Page(index, imageUrl = imageUrl.toString())
}
}
private fun pageListParse(document: Document): List<Page> {
return document.select(pageUrlSelector).mapIndexed { index, element ->
Page(index, document.location(), element.absUrl("src"))
}
}
private fun extractScriptData(document: Document): String {
return document.select("script").map(Element::data)
.firstOrNull(pageRegex::containsMatchIn)
?: throw Exception("Failed to load pages: Script data not found")
private fun pageListParse(document: Document): List<Page> = document.select(pageUrlSelector).mapIndexed { index, element ->
Page(index, document.location(), element.absUrl("src"))
}
private fun extractScriptData(document: Document): String = document.select("script").map(Element::data)
.firstOrNull(pageRegex::containsMatchIn)
?: throw Exception("Failed to load pages: Script data not found")
private fun extractJsonContent(scriptData: String): String {
return pageRegex.find(scriptData)
?.groups?.get(1)?.value
?.let { json.decodeFromString<String>("\"$it\"") }
?: throw Exception("Failed to extract JSON from script")
}
private fun extractJsonContent(scriptData: String): String = pageRegex.find(scriptData)
?.groups?.get(1)?.value
?.let { json.decodeFromString<String>("\"$it\"") }
?: throw Exception("Failed to extract JSON from script")
private fun parseJsonToChapterPageDto(jsonContent: String): ChapterPageDto {
return try {
jsonContent.parseAs<WrapperDto<ChapterPageDto>>().results
} catch (e: Exception) {
throw Exception("Failed to load pages: ${e.message}")
}
private fun parseJsonToChapterPageDto(jsonContent: String): ChapterPageDto = try {
jsonContent.parseAs<WrapperDto<ChapterPageDto>>().results
} catch (e: Exception) {
throw Exception("Failed to load pages: ${e.message}")
}
override fun imageUrlParse(response: Response): String = ""
@ -345,12 +334,13 @@ class SussyToons : HttpSource(), ConfigurableSource {
return json.decodeFromStream(body.byteStream())
}
private inline fun <reified T> String.parseAs(): T {
return json.decodeFromString(this)
}
private inline fun <reified T> String.parseAs(): T = json.decodeFromString(this)
private fun String.toDate() =
try { dateFormat.parse(this)!!.time } catch (_: Exception) { 0L }
private fun String.toDate() = try {
dateFormat.parse(this)!!.time
} catch (_: Exception) {
0L
}
/**
* Normalizes path segments:

View File

@ -41,13 +41,11 @@ class MangaDto(
@SerialName("stt_nome")
val value: String?,
) {
fun toStatus(): Int {
return when (value?.lowercase()) {
"em andamento" -> SManga.ONGOING
"completo" -> SManga.COMPLETED
"hiato" -> SManga.ON_HIATUS
else -> SManga.UNKNOWN
}
fun toStatus(): Int = when (value?.lowercase()) {
"em andamento" -> SManga.ONGOING
"completo" -> SManga.COMPLETED
"hiato" -> SManga.ON_HIATUS
else -> SManga.UNKNOWN
}
}
}

View File

@ -93,8 +93,7 @@ class HattoriManga : HttpSource() {
csrfToken = document.selectFirst("meta[name=csrf-token]")!!.attr("content")
}
override fun chapterListParse(response: Response) =
throw UnsupportedOperationException()
override fun chapterListParse(response: Response) = throw UnsupportedOperationException()
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val slug = manga.url.substringAfterLast('/')
@ -116,10 +115,9 @@ class HattoriManga : HttpSource() {
return Observable.just(chapters)
}
private fun fetchChapterPageableList(slug: String, page: Int, manga: SManga): HMChapterDto =
client.newCall(GET("$baseUrl/load-more-chapters/$slug?page=$page", headers))
.execute()
.parseAs<HMChapterDto>()
private fun fetchChapterPageableList(slug: String, page: Int, manga: SManga): HMChapterDto = client.newCall(GET("$baseUrl/load-more-chapters/$slug?page=$page", headers))
.execute()
.parseAs<HMChapterDto>()
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/latest-chapters")
@ -149,24 +147,20 @@ class HattoriManga : HttpSource() {
}
}
override fun pageListParse(response: Response): List<Page> {
return response.asJsoup().select(".image-wrapper img").mapIndexed { index, element ->
Page(index, imageUrl = "$baseUrl${element.attr("data-src")}")
}.takeIf { it.isNotEmpty() } ?: throw Exception("Oturum açmanız, WebView'ı açmanız ve oturum açmanız gerekir")
}
override fun pageListParse(response: Response): List<Page> = response.asJsoup().select(".image-wrapper img").mapIndexed { index, element ->
Page(index, imageUrl = "$baseUrl${element.attr("data-src")}")
}.takeIf { it.isNotEmpty() } ?: throw Exception("Oturum açmanız, WebView'ı açmanız ve oturum açmanız gerekir")
override fun latestUpdatesParse(response: Response): MangasPage {
return response.use {
val mangas = it.parseAs<HMLatestUpdateDto>().chapters.map {
SManga.create().apply {
val manga = it.manga
title = manga.title
thumbnail_url = "$baseUrl/storage/${manga.thumbnail}"
url = "/manga/${manga.slug}"
}
}.distinctBy { manga -> manga.title }
MangasPage(mangas, false)
}
override fun latestUpdatesParse(response: Response): MangasPage = response.use {
val mangas = it.parseAs<HMLatestUpdateDto>().chapters.map {
SManga.create().apply {
val manga = it.manga
title = manga.title
thumbnail_url = "$baseUrl/storage/${manga.thumbnail}"
url = "/manga/${manga.slug}"
}
}.distinctBy { manga -> manga.title }
MangasPage(mangas, false)
}
override fun popularMangaParse(response: Response): MangasPage {
@ -263,19 +257,18 @@ class HattoriManga : HttpSource() {
setUrlWithoutDomain(REGEX_MANGA_URL.find(script)!!.groups[1]!!.value)
}
private fun parseGenres(document: Document): List<Genre> {
return document.select(".tags-blog a")
.map { element -> Genre(element.text()) }
}
private fun parseGenres(document: Document): List<Genre> = document.select(".tags-blog a")
.map { element -> Genre(element.text()) }
private inline fun <reified T> Response.parseAs(): T {
return json.decodeFromString(body.string())
}
private inline fun <reified T> Response.parseAs(): T = json.decodeFromString(body.string())
private fun Response.isPageExpired() = code == 419
private fun String.toDate(): Long =
try { dateFormat.parse(trim())!!.time } catch (_: Exception) { 0L }
private fun String.toDate(): Long = try {
dateFormat.parse(trim())!!.time
} catch (_: Exception) {
0L
}
class GenreList(title: String, genres: List<Genre>) : Filter.Group<GenreCheckBox>(title, genres.map { GenreCheckBox(it.name, it.id) })

View File

@ -25,7 +25,9 @@ import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat
import java.util.Locale
class LxHentai : ParsedHttpSource(), ConfigurableSource {
class LxHentai :
ParsedHttpSource(),
ConfigurableSource {
override val name = "LXHentai"
@ -41,38 +43,30 @@ class LxHentai : ParsedHttpSource(), ConfigurableSource {
override fun headersBuilder(): Headers.Builder = super.headersBuilder().add("Referer", baseUrl)
override fun popularMangaRequest(page: Int) =
searchMangaRequest(page, "", FilterList(SortBy(3)))
override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", FilterList(SortBy(3)))
override fun popularMangaSelector() = searchMangaSelector()
override fun popularMangaFromElement(element: Element) =
searchMangaFromElement(element)
override fun popularMangaFromElement(element: Element) = searchMangaFromElement(element)
override fun popularMangaNextPageSelector() =
searchMangaNextPageSelector()
override fun popularMangaNextPageSelector() = searchMangaNextPageSelector()
override fun latestUpdatesRequest(page: Int) =
searchMangaRequest(page, "", FilterList(SortBy(0)))
override fun latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", FilterList(SortBy(0)))
override fun latestUpdatesSelector() = searchMangaSelector()
override fun latestUpdatesFromElement(element: Element) =
searchMangaFromElement(element)
override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element)
override fun latestUpdatesNextPageSelector() =
searchMangaNextPageSelector()
override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector()
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return when {
query.startsWith(PREFIX_ID_SEARCH) -> {
val slug = query.substringAfter(PREFIX_ID_SEARCH)
val mangaUrl = "/truyen/$slug"
fetchMangaDetails(SManga.create().apply { url = mangaUrl })
.map { MangasPage(listOf(it), false) }
}
else -> super.fetchSearchManga(page, query, filters)
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = when {
query.startsWith(PREFIX_ID_SEARCH) -> {
val slug = query.substringAfter(PREFIX_ID_SEARCH)
val mangaUrl = "/truyen/$slug"
fetchMangaDetails(SManga.create().apply { url = mangaUrl })
.map { MangasPage(listOf(it), false) }
}
else -> super.fetchSearchManga(page, query, filters)
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
@ -168,32 +162,33 @@ class LxHentai : ParsedHttpSource(), ConfigurableSource {
private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>, state: Int = 0) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), state) {
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>, state: Int = 0) : Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), state) {
fun toUriPart() = vals[state].second
}
private class SortBy(state: Int = 0) : UriPartFilter(
"Sắp xếp theo",
arrayOf(
Pair("Mới cập nhật", "-updated_at"),
Pair("Mới nhất", "-created_at"),
Pair("Cũ nhất", "created_at"),
Pair("Xem nhiều", "-views"),
Pair("A-Z", "name"),
Pair("Z-A", "-name"),
),
state,
)
private class SortBy(state: Int = 0) :
UriPartFilter(
"Sắp xếp theo",
arrayOf(
Pair("Mới cập nhật", "-updated_at"),
Pair("Mới nhất", "-created_at"),
Pair("Cũ nhất", "created_at"),
Pair("Xem nhiều", "-views"),
Pair("A-Z", "name"),
Pair("Z-A", "-name"),
),
state,
)
private class Status : UriPartFilter(
"Trạng thái",
arrayOf(
Pair("Tất cả", "1,2"),
Pair("Đang tiến hành", "2"),
Pair("Đã hoàn thành", "1"),
),
)
private class Status :
UriPartFilter(
"Trạng thái",
arrayOf(
Pair("Tất cả", "1,2"),
Pair("Đang tiến hành", "2"),
Pair("Đã hoàn thành", "1"),
),
)
private class Genre(name: String, val id: Int) : Filter.TriState(name)
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Thể loại", genres)

View File

@ -34,39 +34,31 @@ class TruyenTranh3Q : ParsedHttpSource() {
.rateLimit(3)
.build()
override fun headersBuilder(): Headers.Builder {
return super.headersBuilder().add("Referer", "$baseUrl/")
}
override fun headersBuilder(): Headers.Builder = super.headersBuilder().add("Referer", "$baseUrl/")
private val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.US)
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/danh-sach/truyen-yeu-thich?page=$page", headers)
}
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/danh-sach/truyen-yeu-thich?page=$page", headers)
override fun popularMangaSelector(): String = "ul.list_grid.grid > li"
override fun popularMangaFromElement(element: Element): SManga {
return SManga.create().apply {
element.select("h3 a").let {
title = it.text()
setUrlWithoutDomain(it.attr("abs:href"))
}
thumbnail_url = element.selectFirst(".book_avatar a img")
?.absUrl("src")
?.let { url ->
url.toHttpUrlOrNull()
?.queryParameter("url")
?: url
}
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
element.select("h3 a").let {
title = it.text()
setUrlWithoutDomain(it.attr("abs:href"))
}
thumbnail_url = element.selectFirst(".book_avatar a img")
?.absUrl("src")
?.let { url ->
url.toHttpUrlOrNull()
?.queryParameter("url")
?: url
}
}
override fun popularMangaNextPageSelector(): String? = ".page_redirect > a:last-child > p:not(.active)"
override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/danh-sach/truyen-moi-cap-nhat?page=$page", headers)
}
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/danh-sach/truyen-moi-cap-nhat?page=$page", headers)
// same as popularManga
override fun latestUpdatesSelector(): String = popularMangaSelector()
@ -121,21 +113,19 @@ class TruyenTranh3Q : ParsedHttpSource() {
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector()
override fun mangaDetailsParse(document: Document): SManga {
return SManga.create().apply {
document.selectFirst(".book_info > .book_other")?.let { info ->
title = info.selectFirst("h1[itemprop=name]")!!.text()
author = info.selectFirst("ul.list-info li.author p.col-xs-9")?.text()
status = when (info.selectFirst("ul.list-info li.status p.col-xs-9")?.text()) {
"Đang Cập Nhật" -> SManga.ONGOING
"Hoàn Thành" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
genre = info.select(".list01 li a").joinToString { it.text() }
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
document.selectFirst(".book_info > .book_other")?.let { info ->
title = info.selectFirst("h1[itemprop=name]")!!.text()
author = info.selectFirst("ul.list-info li.author p.col-xs-9")?.text()
status = when (info.selectFirst("ul.list-info li.status p.col-xs-9")?.text()) {
"Đang Cập Nhật" -> SManga.ONGOING
"Hoàn Thành" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
description = document.selectFirst(".book_detail > .story-detail-info")?.text()
thumbnail_url = document.selectFirst(".book_detail > .book_info > .book_avatar > img")?.attr("abs:src")
genre = info.select(".list01 li a").joinToString { it.text() }
}
description = document.selectFirst(".book_detail > .story-detail-info")?.text()
thumbnail_url = document.selectFirst(".book_detail > .book_info > .book_avatar > img")?.attr("abs:src")
}
// chapters
@ -183,23 +173,19 @@ class TruyenTranh3Q : ParsedHttpSource() {
}
}
override fun chapterFromElement(element: Element): SChapter {
return SChapter.create().apply {
element.selectFirst(".name-chap > a")?.let {
name = it.text()
setUrlWithoutDomain(it.attr("abs:href"))
}
date_upload = parseChapterDate(element.selectFirst(".time-chap")?.text() ?: "")
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
element.selectFirst(".name-chap > a")?.let {
name = it.text()
setUrlWithoutDomain(it.attr("abs:href"))
}
date_upload = parseChapterDate(element.selectFirst(".time-chap")?.text() ?: "")
}
// parse pages
private val pageListSelector = ".chapter_content .page-chapter img"
override fun pageListParse(document: Document): List<Page> {
return document.select(pageListSelector).mapIndexed { idx, it ->
Page(idx, imageUrl = it.absUrl("data-src"))
}
override fun pageListParse(document: Document): List<Page> = document.select(pageListSelector).mapIndexed { idx, it ->
Page(idx, imageUrl = it.absUrl("data-src"))
}
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
@ -248,10 +234,8 @@ class TruyenTranh3Q : ParsedHttpSource() {
private fun genresRequest() = GET("$baseUrl/$searchPath", headers)
private fun parseGenres(document: Document): List<Genre> {
return document.select(".genre-item").mapIndexed { index, element ->
Genre(element.text(), index + 1)
}
private fun parseGenres(document: Document): List<Genre> = document.select(".genre-item").mapIndexed { index, element ->
Genre(element.text(), index + 1)
}
private fun fetchGenres() {

View File

@ -36,7 +36,9 @@ import java.io.IOException
import java.net.URLDecoder
import java.util.concurrent.TimeUnit
class YuriNeko : HttpSource(), ConfigurableSource {
class YuriNeko :
HttpSource(),
ConfigurableSource {
override val name = "YuriNeko"
@ -107,22 +109,20 @@ class YuriNeko : HttpSource(), ConfigurableSource {
override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException()
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return when {
query.startsWith(PREFIX_ID_SEARCH) -> {
val id = query.removePrefix(PREFIX_ID_SEARCH).trim()
if (id.toIntOrNull() == null) {
throw Exception("ID tìm kiếm không hợp lệ (phải là một số).")
}
fetchMangaDetails(
SManga.create().apply {
url = "/manga/$id"
},
)
.map { MangasPage(listOf(it), false) }
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = when {
query.startsWith(PREFIX_ID_SEARCH) -> {
val id = query.removePrefix(PREFIX_ID_SEARCH).trim()
if (id.toIntOrNull() == null) {
throw Exception("ID tìm kiếm không hợp lệ (phải là một số).")
}
else -> super.fetchSearchManga(page, query, filters)
fetchMangaDetails(
SManga.create().apply {
url = "/manga/$id"
},
)
.map { MangasPage(listOf(it), false) }
}
else -> super.fetchSearchManga(page, query, filters)
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
@ -182,15 +182,13 @@ class YuriNeko : HttpSource(), ConfigurableSource {
override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
override fun fetchMangaDetails(manga: SManga): Observable<SManga> =
client.newCall(GET("$apiUrl${manga.url}"))
.asObservableSuccess()
.map { mangaDetailsParse(it) }
override fun fetchMangaDetails(manga: SManga): Observable<SManga> = client.newCall(GET("$apiUrl${manga.url}"))
.asObservableSuccess()
.map { mangaDetailsParse(it) }
override fun mangaDetailsRequest(manga: SManga): Request = GET("$baseUrl${manga.url}")
override fun mangaDetailsParse(response: Response): SManga =
response.parseAs<MangaDto>().toSManga(storageUrl)
override fun mangaDetailsParse(response: Response): SManga = response.parseAs<MangaDto>().toSManga(storageUrl)
override fun chapterListRequest(manga: SManga): Request = GET("$apiUrl${manga.url}")
@ -202,13 +200,11 @@ class YuriNeko : HttpSource(), ConfigurableSource {
override fun pageListRequest(chapter: SChapter): Request = GET("$apiUrl${chapter.url}")
override fun pageListParse(response: Response): List<Page> =
response.parseAs<ReadResponseDto>().toPageList(storageUrl)
override fun pageListParse(response: Response): List<Page> = response.parseAs<ReadResponseDto>().toPageList(storageUrl)
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) : Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}