ru/AComics: Fix NPEs (#941)

* fix: Fix manga details

* fix: Fix chapter list

* fix: Fix page selector

* refactor: General refactoration

* chore: Set isNsfw flag

* chore: Bump version

* refactor: Apply suggestion
This commit is contained in:
Claudemirovsky 2024-02-03 14:01:04 -03:00 committed by Draff
parent 246fe574e7
commit 8f7c88a723
2 changed files with 100 additions and 104 deletions

View File

@ -1,7 +1,8 @@
ext { ext {
extName = 'AComics' extName = 'AComics'
extClass = '.AComics' extClass = '.AComics'
extVersionCode = 3 extVersionCode = 4
isNsfw = true
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -8,11 +8,11 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.net.URLEncoder
class AComics : ParsedHttpSource() { class AComics : ParsedHttpSource() {
@ -22,23 +22,12 @@ class AComics : ParsedHttpSource() {
override val lang = "ru" override val lang = "ru"
private val cookiesHeader by lazy {
val cookies = mutableMapOf<String, String>()
cookies["ageRestrict"] = "17"
buildCookies(cookies)
}
private fun buildCookies(cookies: Map<String, String>) =
cookies.entries.joinToString(separator = "; ", postfix = ";") {
"${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}"
}
override val client = network.client.newBuilder() override val client = network.client.newBuilder()
.addNetworkInterceptor { chain -> .addNetworkInterceptor { chain ->
val newReq = chain val newReq = chain
.request() .request()
.newBuilder() .newBuilder()
.addHeader("Cookie", cookiesHeader) .addHeader("Cookie", "ageRestrict=17;")
.build() .build()
chain.proceed(newReq) chain.proceed(newReq)
@ -46,153 +35,159 @@ class AComics : ParsedHttpSource() {
override val supportsLatest = true override val supportsLatest = true
// ============================== Popular ===============================
override fun popularMangaRequest(page: Int): Request = override fun popularMangaRequest(page: Int): Request =
GET("$baseUrl/comics?categories=&ratings[]=1&ratings[]=2&ratings[]=3&ratings[]=4&ratings[]=5ratings[]=6&&type=0&updatable=0&subscribe=0&issue_count=2&sort=subscr_count&skip=${10 * (page - 1)}", headers) GET("$baseUrl/comics?$DEFAULT_COMIC_QUERIES&sort=subscr_count&skip=${10 * (page - 1)}", headers)
override fun popularMangaSelector() = "table.list-loadable > tbody > tr"
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
thumbnail_url = element.selectFirst("a > img")?.absUrl("src")
element.selectFirst("div.title > a")!!.run {
setUrlWithoutDomain(attr("href") + "/about")
title = text()
}
}
override fun popularMangaNextPageSelector() = "span.button:not(:has(a)) + span.button > a"
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request = override fun latestUpdatesRequest(page: Int): Request =
GET("$baseUrl/comics?categories=&ratings[]=1&ratings[]=2&ratings[]=3&ratings[]=4&ratings[]=5ratings[]=6&&type=0&updatable=0&subscribe=0&issue_count=2&sort=last_update&skip=${10 * (page - 1)}", headers) GET("$baseUrl/comics?$DEFAULT_COMIC_QUERIES&sort=last_update&skip=${10 * (page - 1)}", headers)
override fun latestUpdatesSelector() = popularMangaSelector()
override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
// =============================== Search ===============================
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url: String = if (query.isNotEmpty()) { val url = if (query.isNotEmpty()) {
"$baseUrl/search?keyword=$query" "$baseUrl/search?keyword=$query"
} else { } else {
val categories = mutableListOf<Int>() val urlBuilder = "$baseUrl/comics?type=0&subscribe=0&issue_count=2&sort=subscr_count"
var status = "0" .toHttpUrl()
val rating = mutableListOf<Int>() .newBuilder()
.addQueryParameter("skip", "${10 * (page - 1)}")
for (filter in if (filters.isEmpty()) getFilterList() else filters) { for (filter in if (filters.isEmpty()) getFilterList() else filters) {
when (filter) { when (filter) {
is GenreList -> { is GenreList -> {
filter.state.forEach { val categories = filter.state.filter { it.state }.joinToString(",") { it.id }
if (it.state) { urlBuilder.addQueryParameter("categories", categories)
categories.add(it.id)
}
}
} }
is Status -> { is Status -> {
if (filter.state == 1) { val status = when (filter.state) {
status = "no" 1 -> "no"
} 2 -> "yes"
if (filter.state == 2) { else -> "0"
status = "yes"
} }
urlBuilder.addQueryParameter("updatable", status)
} }
is RatingList -> { is RatingList -> {
filter.state.forEach { filter.state.forEach {
if (it.state) { if (it.state) {
rating.add(it.id) urlBuilder.addQueryParameter("ratings[]", it.id)
} }
} }
} }
else -> {} else -> {}
} }
} }
"$baseUrl/comics?categories=${categories.joinToString(",")}&${rating.joinToString { "ratings[]=$it" }}&type=0&updatable=$status&subscribe=0&issue_count=2&sort=subscr_count&skip=${10 * (page - 1)}" urlBuilder.build().toString()
} }
return GET(url, headers) return GET(url, headers)
} }
override fun popularMangaSelector() = "table.list-loadable > tbody > tr"
override fun latestUpdatesSelector() = popularMangaSelector()
override fun searchMangaSelector() = popularMangaSelector() override fun searchMangaSelector() = popularMangaSelector()
override fun popularMangaFromElement(element: Element): SManga { override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
val manga = SManga.create()
manga.thumbnail_url = baseUrl + element.select("a > img").first()!!.attr("src")
element.select("div.title > a").first()!!.let {
manga.setUrlWithoutDomain(it.attr("href") + "/about")
manga.title = it.text()
}
return manga
}
override fun latestUpdatesFromElement(element: Element): SManga =
popularMangaFromElement(element)
override fun searchMangaFromElement(element: Element): SManga =
popularMangaFromElement(element)
override fun popularMangaNextPageSelector() = "span.button:not(:has(a)) + span.button > a"
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun mangaDetailsParse(document: Document): SManga { // =========================== Manga Details ============================
val infoElement = document.select(".about-summary").first()!! override fun mangaDetailsParse(document: Document) = SManga.create().apply {
val manga = SManga.create() val article = document.selectFirst("article.common-article")!!
manga.author = infoElement.select(".about-summary > p:contains(Автор)").text().split(":")[1] with(article) {
manga.genre = infoElement.select("a.button").joinToString { it.text() } title = selectFirst(".page-header-with-menu h1")!!.text()
manga.description = infoElement.ownText() genre = select("p.serial-about-badges a.category").joinToString { it.text() }
return manga author = select("p.serial-about-authors a, p:contains(Автор оригинала)").joinToString { it.ownText() }
description = selectFirst("section.serial-about-text")?.text()
}
} }
// ============================== Chapters ==============================
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val res = mutableListOf<SChapter>() val doc = response.asJsoup()
val count = response.asJsoup() val count = doc
.select(".about-summary > p:contains(Количество выпусков:)") .selectFirst("p:has(b:contains(Количество выпусков:))")!!
.text() .ownText()
.split("Количество выпусков: ")[1].toInt() .toInt()
for (index in count downTo 1) { val comicPath = doc.location().substringBefore("/about")
val chapter = SChapter.create()
chapter.chapter_number = index.toFloat() return (count downTo 1).map {
chapter.name = index.toString() SChapter.create().apply {
val url = response.request.url.toString().split("/about")[0].split(baseUrl)[1] chapter_number = it.toFloat()
chapter.url = "$url/$index" name = it.toString()
res.add(chapter) setUrlWithoutDomain("$comicPath/$it")
}
} }
return res
} }
override fun chapterListSelector(): Nothing = throw UnsupportedOperationException() override fun chapterListSelector(): Nothing = throw UnsupportedOperationException()
override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException() override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException()
// =============================== Pages ================================
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val imageElement = document.select("img#mainImage").first()!! val imageElement = document.selectFirst("img.issue")!!
return listOf(Page(0, imageUrl = baseUrl + imageElement.attr("src"))) return listOf(Page(0, imageUrl = imageElement.absUrl("src")))
} }
override fun imageUrlParse(document: Document) = "" override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Категории", genres) // ============================== Filters ===============================
private class Genre(name: String, val id: Int) : Filter.CheckBox(name) private class Genre(name: String, val id: String) : Filter.CheckBox(name)
private class Rating(name: String, val id: Int) : Filter.CheckBox(name, state = true) private class Rating(name: String, val id: String) : Filter.CheckBox(name, state = true)
private class Status : Filter.Select<String>("Статус", arrayOf("Все", "Завершенный", "Продолжающийся")) private class Status : Filter.Select<String>("Статус", arrayOf("Все", "Завершенный", "Продолжающийся"))
private class GenreList : Filter.Group<Genre>(
"Категории",
listOf(
Genre("Животные", "1"),
Genre("Драма", "2"),
Genre("Фэнтези", "3"),
Genre("Игры", "4"),
Genre("Юмор", "5"),
Genre("Журнал", "6"),
Genre("Паранормальное", "7"),
Genre("Конец света", "8"),
Genre("Романтика", "9"),
Genre("Фантастика", "10"),
Genre("Бытовое", "11"),
Genre("Стимпанк", "12"),
Genre("Супергерои", "13"),
),
)
private class RatingList : Filter.Group<Rating>( private class RatingList : Filter.Group<Rating>(
"Возрастная категория", "Возрастная категория",
listOf( listOf(
Rating("???", 1), Rating("???", "1"),
Rating("0+", 2), Rating("0+", "2"),
Rating("6+", 3), Rating("6+", "3"),
Rating("12+", 4), Rating("12+", "4"),
Rating("16+", 5), Rating("16+", "5"),
Rating("18+", 6), Rating("18+", "6"),
), ),
) )
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
Status(), Status(),
RatingList(), RatingList(),
GenreList(getGenreList()), GenreList(),
)
private fun getGenreList() = listOf(
Genre("Животные", 1),
Genre("Драма", 2),
Genre("Фэнтези", 3),
Genre("Игры", 4),
Genre("Юмор", 5),
Genre("Журнал", 6),
Genre("Паранормальное", 7),
Genre("Конец света", 8),
Genre("Романтика", 9),
Genre("Фантастика", 10),
Genre("Бытовое", 11),
Genre("Стимпанк", 12),
Genre("Супергерои", 13),
) )
} }
private const val DEFAULT_COMIC_QUERIES = "categories=&ratings[]=1&ratings[]=2&ratings[]=3&ratings[]=4&ratings[]=5&ratings[]=6&type=0&updatable=0&issue_count=2"