From 51b2cc75b52d8c9da63ac8947bc7cc00ceb54cfd Mon Sep 17 00:00:00 2001 From: Ejan <35057681+e-shl@users.noreply.github.com> Date: Mon, 9 May 2022 18:14:13 +0500 Subject: [PATCH] [RU]UniComics Events & Filters parse (break id/url in library) (#11781) * [RU]UniComics filters * filter list * extVersionCode * no blank search * baseDefaultUrl * simple CheckboxCaptcha * Events parse/filters (break id/url in library) * search fixes * only * toUpperCase * fix/change chapter naming * chapter_number substringAfterLast * fix open WebView --- src/ru/unicomics/build.gradle | 2 +- .../extension/ru/unicomics/UniComics.kt | 180 ++++++++++++++---- 2 files changed, 147 insertions(+), 35 deletions(-) diff --git a/src/ru/unicomics/build.gradle b/src/ru/unicomics/build.gradle index a7249e8fd..c93714fd3 100644 --- a/src/ru/unicomics/build.gradle +++ b/src/ru/unicomics/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'UniComics' pkgNameSuffix = 'ru.unicomics' extClass = '.UniComics' - extVersionCode = 2 + extVersionCode = 3 } dependencies { diff --git a/src/ru/unicomics/src/eu/kanade/tachiyomi/extension/ru/unicomics/UniComics.kt b/src/ru/unicomics/src/eu/kanade/tachiyomi/extension/ru/unicomics/UniComics.kt index af3ca6207..9645b7760 100644 --- a/src/ru/unicomics/src/eu/kanade/tachiyomi/extension/ru/unicomics/UniComics.kt +++ b/src/ru/unicomics/src/eu/kanade/tachiyomi/extension/ru/unicomics/UniComics.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension.ru.unicomics import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.asObservableSuccess +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 @@ -11,6 +12,7 @@ import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response @@ -46,11 +48,36 @@ class UniComics : ParsedHttpSource() { override fun latestUpdatesRequest(page: Int): Request = GET("$baseDefaultUrl/comics/online/page/$page", headers) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = - GET("https://yandex.ru/search/site/?frame=1&lr=172&searchid=1959358&topdoc=xdm_e=$baseDefaultUrl&xdm_c=default5044&xdm_p=1&v=2.0&web=0&text=$query&p=$page", headers) - + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> + when (filter) { + is getEventsList -> { + if (filter.state > 0) { + return GET("$baseDefaultUrl$PATH_events", headers) + } + } + is Publishers -> { + if (filter.state > 0) { + val publisherName = getPublishersList()[filter.state].url + val publisherUrl = + "$baseDefaultUrl$PATH_publishers/$publisherName/page/$page".toHttpUrlOrNull()!! + .newBuilder() + return GET(publisherUrl.toString(), headers) + } + } + else -> return@forEach + } + } + if (query.isNotEmpty()) { + return GET( + "https://yandex.ru/search/site/?frame=1&lr=172&searchid=1959358&topdoc=xdm_e=$baseDefaultUrl&xdm_c=default5044&xdm_p=1&v=2.0&web=0&text=$query&p=$page", + headers + ) + } + return popularMangaRequest(page) + } override fun searchMangaSelector() = - ".b-serp-item__content:has(.b-serp-url__item:contains(/comics/):not(:contains(/comics/events)):not(:contains(/comics/publishers)):not(:contains(/page/))):has(.b-serp-item__title-link:not(:contains(Комиксы читать онлайн бесплатно)))" + ".b-serp-item__content:has(.b-serp-url__item:contains(/comics/):not(:contains($PATH_events)):not(:contains($PATH_publishers)):not(:contains(/page/))):has(.b-serp-item__title-link:not(:contains(Комиксы читать онлайн бесплатно)))" override fun searchMangaNextPageSelector() = ".b-pager__next" override fun searchMangaFromElement(element: Element): SManga { @@ -61,7 +88,7 @@ class UniComics : ParsedHttpSource() { "/characters$|/creators$".toRegex().replace( "/page$".toRegex().replace( "/[0-9]+/?$".toRegex().replace( - originUrl.substringAfter(PATH_URL).substringAfter(PATH_online).substringAfter(PATH_issue), "" + originUrl.replace(PATH_online, PATH_URL).replace(PATH_issue, PATH_URL), "" ), "" ), @@ -81,18 +108,37 @@ class UniComics : ParsedHttpSource() { override fun searchMangaParse(response: Response): MangasPage { val document = response.asJsoup() - if (document.select(".CheckboxCaptcha").isNotEmpty() && baseUrl == baseDefaultUrl) { + + if (document.location().contains("$baseDefaultUrl$PATH_events")) { + val mangas = document.select(".list_events").map { element -> + SManga.create().apply { + element.select("a").first().let { + setUrlWithoutDomain("/" + it.attr("href")) + title = it.text() + } + } + } + return MangasPage(mangas, false) + } + + if (document.location().contains("$baseDefaultUrl/comics")) { + val mangas = document.select(popularMangaSelector()).map { element -> + popularMangaFromElement(element) + } + return MangasPage(mangas, mangas.isNotEmpty()) + } + + if (document.select(".CheckboxCaptcha").isNotEmpty()) { baseUrl = document.location() throw Exception("Пройдите капчу в WebView(слишком много запросов)") } else if (baseUrl != baseDefaultUrl) { baseUrl = baseDefaultUrl } - var hasNextPage = false - val mangas = document.select(searchMangaSelector()).map { element -> searchMangaFromElement(element) } + var hasNextPage = false val nextSearchPage = document.select(searchMangaNextPageSelector()) if (nextSearchPage.isNotEmpty()) { hasNextPage = true @@ -111,7 +157,7 @@ class UniComics : ParsedHttpSource() { .asObservableSuccess() .map { response -> val details = mangaDetailsParse(response) - details.url = realQuery + details.url = PATH_URL + realQuery MangasPage(listOf(details), false) } } else { @@ -129,21 +175,14 @@ class UniComics : ParsedHttpSource() { override fun popularMangaFromElement(element: Element): SManga { val manga = SManga.create() - manga.thumbnail_url = element.select(".left_comics img").first().attr("src").replace(".jpg", "_big.jpg") + manga.thumbnail_url = element.select(".left_comics img")!!.first().attr("src").replace(".jpg", "_big.jpg") element.select("a").first().let { - manga.setUrlWithoutDomain(it.attr("href").substringAfter(PATH_URL)) + manga.setUrlWithoutDomain(it.attr("href")) } manga.title = element.select(".list_title").first().text() return manga } - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangas = document.select(popularMangaSelector()).map { element -> - popularMangaFromElement(element) - } - return MangasPage(mangas, true) - } + override fun popularMangaParse(response: Response) = searchMangaParse(response) override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response) @@ -153,20 +192,23 @@ class UniComics : ParsedHttpSource() { override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() override fun mangaDetailsRequest(manga: SManga): Request { - return GET(baseDefaultUrl + PATH_URL + manga.url, headers) + return GET(baseDefaultUrl + manga.url, headers) } override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val infoElement = document.select(".block.left.common").first() + val infoElement = document.select(".left_container div").first() title = infoElement.select("h1").first().text() - thumbnail_url = infoElement.select("img").first().attr("src") + thumbnail_url = if (infoElement.select("img").isNotEmpty()) + infoElement.select("img").first().attr("src") + else + document.select(".left_comics img").first().attr("src").replace(".jpg", "_big.jpg") description = infoElement.select("p").last()?.text() author = infoElement.select("tr:contains(Издательство)").text() genre = infoElement.select("tr:contains(Жанр) a").joinToString { it.text() } } override fun fetchChapterList(manga: SManga): Observable> { - val document = client.newCall(GET(baseDefaultUrl + PATH_URL + manga.url, headers)).execute().asJsoup() + val document = client.newCall(GET(baseDefaultUrl + manga.url, headers)).execute().asJsoup() val pages = mutableListOf(1) val dataStrArray = document.toString() .substringAfter("new Paginator(") @@ -177,33 +219,41 @@ class UniComics : ParsedHttpSource() { } return Observable.just( pages.flatMap { page -> - chapterListParse(client.newCall(chapterPageListRequest(manga, page)).execute()) + chapterListParse(client.newCall(chapterPageListRequest(manga, page)).execute(), manga) }.reversed() ) } private fun chapterPageListRequest(manga: SManga, page: Int): Request { - return GET("$baseDefaultUrl$PATH_URL${manga.url}/page/$page", headers) + return GET("$baseDefaultUrl${manga.url}/page/$page", headers) } override fun chapterListSelector() = "div.right_comics" - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select(".button.online a").first() + private fun chapterListParse(response: Response, manga: SManga): List { + val document = response.asJsoup() + return document.select(chapterListSelector()).map { chapterFromElement(it, manga) } + } + + private fun chapterFromElement(element: Element, manga: SManga): SChapter { + val urlElement = element.select("td:eq(1) a") val chapter = SChapter.create() element.select(".list_title").first().text().let { - if (it.contains(" №")) { - chapter.name = it.substringAfterLast(" ") - chapter.chapter_number = it.substringAfter(" №").toFloatOrNull() ?: -1f - } else { - chapter.name = "$it Сингл" - chapter.chapter_number = 0f + val titleNoPrefix = it.removePrefix(manga.title).removePrefix(":").trim() + chapter.name = if (titleNoPrefix.isNotEmpty()) + titleNoPrefix.replaceFirst(titleNoPrefix.first(), titleNoPrefix.first().toUpperCase()) + else + "Сингл" + if (titleNoPrefix.contains("№")) { + chapter.chapter_number = titleNoPrefix.substringAfterLast("№").toFloatOrNull() ?: -1f } } chapter.setUrlWithoutDomain(urlElement.attr("href")) return chapter } + override fun chapterFromElement(element: Element) = throw UnsupportedOperationException("Not used") + override fun pageListRequest(chapter: SChapter): Request { return GET(baseDefaultUrl + chapter.url, headers) } @@ -218,13 +268,75 @@ class UniComics : ParsedHttpSource() { } } override fun imageUrlParse(document: Document): String { - return document.select("#b_image").attr("src") + return document.select(".image_online").attr("src") } + private class Publishers(publishers: Array) : Filter.Select("Издательства (только)", publishers) + + override fun getFilterList() = FilterList( + Publishers(publishersName), + getEventsList() + ) + private class getEventsList : Filter.Select( + "События (только)", + arrayOf("Нет", "в комиксах") + ) + + private data class Publisher(val name: String, val url: String) + + private fun getPublishersList() = listOf( + Publisher("Все", "not"), + Publisher("Marvel", "marvel"), + Publisher("DC Comics", "dc"), + Publisher("Image Comics", "imagecomics"), + Publisher("Dark Horse Comics", "dark-horse-comics"), + Publisher("IDW Publishing", "idw"), + Publisher("Vertigo", "vertigo"), + Publisher("WildStorm", "wildstorm"), + Publisher("Dynamite Entertainment", "dynamite"), + Publisher("Boom! Studios", "boomstudios"), + Publisher("Avatar Press", "avatarpress"), + Publisher("Fox Atomicg", "foxatomic"), + Publisher("Top Shelf Productions", "topshelfproduct"), + Publisher("Topps", "topps"), + Publisher("Radical Publishing", "radical-publishing"), + Publisher("Top Cow", "top-cow"), + Publisher("Zenescope Entertainment", "zenescope"), + Publisher("88MPH", "88mph"), + Publisher("Soleil", "soleil"), + Publisher("Warner Bros. Entertainment", "warner-bros"), + Publisher("Ubisoft Entertainment", "ubisoft"), + Publisher("Oni Press", "oni-press"), + Publisher("Armada", "delcourt"), + Publisher("Heavy Metal", "heavy-metal"), + Publisher("Harris Comics", "harris-comics"), + Publisher("Antarctic Press", "antarctic-press"), + Publisher("Valiant", "valiant"), + Publisher("Disney", "disney"), + Publisher("Malibu", "malibu"), + Publisher("Slave Labor", "slave-labor"), + Publisher("Nbm", "nbm"), + Publisher("Viper Comics", "viper-comics"), + Publisher("Random House", "random-house"), + Publisher("Active Images", "active-images"), + Publisher("Eurotica", "eurotica"), + Publisher("Vortex", "vortex"), + Publisher("Fantagraphics", "fantagraphics"), + Publisher("Epic", "epic"), + Publisher("Warp Graphics", "warp-graphics"), + Publisher("Scholastic Book Services", "scholastic-book-services"), + Publisher("Ballantine Books", "ballantine-books"), + Publisher("Id Software", "id-software") + ) + private val publishersName = getPublishersList().map { + it.name + }.toTypedArray() companion object { const val PREFIX_SLUG_SEARCH = "slug:" private const val PATH_URL = "/comics/series/" private const val PATH_online = "/comics/online/" private const val PATH_issue = "/comics/issue/" + private const val PATH_publishers = "/comics/publishers" + private const val PATH_events = "/comics/events" } }