From af114d74df20dbb60d0f4c1fe3a58cad109a0de5 Mon Sep 17 00:00:00 2001 From: Zakhar Timoshenko Date: Sun, 3 Oct 2021 20:25:08 +0300 Subject: [PATCH] Add new source: MangaOwl --- .../kotatsu/core/model/MangaSource.kt | 3 +- .../kotatsu/core/parser/ParserModule.kt | 1 + .../core/parser/site/MangaOwlRepository.kt | 159 ++++++++++++++++++ 3 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaOwlRepository.kt diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt index f881a59f0..10cc61c91 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt @@ -39,7 +39,8 @@ enum class MangaSource( NINEMANGA_IT("NineManga Italiano", "it", NineMangaRepository.Italiano::class.java), NINEMANGA_BR("NineManga Brasil", "pt", NineMangaRepository.Brazil::class.java), NINEMANGA_FR("NineManga Français", "fr", NineMangaRepository.Francais::class.java), - EXHENTAI("ExHentai", null, ExHentaiRepository::class.java) + EXHENTAI("ExHentai", null, ExHentaiRepository::class.java), + MANGAOWL("MangaOwl", "en", MangaOwlRepository::class.java) ; @get:Throws(NoBeanDefFoundException::class) diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/ParserModule.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/ParserModule.kt index 361bdfa61..d63676a44 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/ParserModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/ParserModule.kt @@ -33,4 +33,5 @@ val parserModule factory(named(MangaSource.NINEMANGA_IT)) { NineMangaRepository.Italiano(get()) } factory(named(MangaSource.NINEMANGA_FR)) { NineMangaRepository.Francais(get()) } factory(named(MangaSource.EXHENTAI)) { ExHentaiRepository(get()) } + factory(named(MangaSource.MANGAOWL)) { MangaOwlRepository(get()) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaOwlRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaOwlRepository.kt new file mode 100644 index 000000000..768463562 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaOwlRepository.kt @@ -0,0 +1,159 @@ +package org.koitharu.kotatsu.core.parser.site + +import org.koitharu.kotatsu.base.domain.MangaLoaderContext +import org.koitharu.kotatsu.core.exceptions.ParseException +import org.koitharu.kotatsu.core.model.* +import org.koitharu.kotatsu.core.parser.RemoteMangaRepository +import org.koitharu.kotatsu.utils.ext.* +import java.util.* + +class MangaOwlRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepository(loaderContext) { + + override val source = MangaSource.MANGAOWL + + override val defaultDomain = "mangaowls.com" + + override val sortOrders: Set = EnumSet.of( + SortOrder.POPULARITY, + SortOrder.NEWEST, + SortOrder.UPDATED + ) + + override suspend fun getList2( + offset: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder?, + ): List { + val page = (offset / 36f).toIntUp().inc() + val link = buildString { + append("https://") + append(getDomain()) + when { + !query.isNullOrEmpty() -> { + append("/search/${page}?search=") + append(query.urlEncoded()) + } + !tags.isNullOrEmpty() -> { + for (tag in tags) { + append(tag.key) + } + append("/${page}?type=${getAlternativeSortKey(sortOrder)}") + } + else -> { + append("/${getSortKey(sortOrder)}/${page}") + } + } + } + val doc = loaderContext.httpGet(link).parseHtml() + val slides = doc.body().select("ul.slides") ?: parseFailed("An error occurred while parsing") + val items = slides.select("div.col-md-2") + return items.mapNotNull { item -> + val href = item.select("h6 a").attr("href") ?: return@mapNotNull null + Manga( + id = generateUid(href), + title = item.select("h6 a").text(), + coverUrl = item.select("div.img-responsive").attr("abs:data-background-image"), + altTitle = item.select("h6 a").attr("alt") ?: return@mapNotNull null, + author = null, + rating = runCatching { + item.selectFirst("div.block-stars") + ?.text() + ?.toFloatOrNull() + ?.div(10f) + }.getOrNull() ?: Manga.NO_RATING, + url = href, + publicUrl = href.withDomain(), + source = source + ) + } + } + + override suspend fun getDetails(manga: Manga): Manga { + val doc = loaderContext.httpGet(manga.publicUrl).parseHtml() + val info = doc.body().selectFirst("div.single_detail") ?: parseFailed("An error occurred while parsing") + val table = doc.body().selectFirst("div.single-grid-right") ?: parseFailed("An error occurred while parsing") + return manga.copy( + description = info.selectFirst(".description")?.html(), + largeCoverUrl = info.select("img").first()?.let { img -> + if (img.hasAttr("data-src")) img.attr("abs:data-src") else img.attr("abs:src") + }, + author = info.select("p.fexi_header_para a.author_link").text(), + state = parseStatus(info.select("p.fexi_header_para:contains(status)").first()?.ownText()), + tags = manga.tags + info.select("div.col-xs-12.col-md-8.single-right-grid-right > p > a[href*=genres]") + .mapNotNull { + val a = it.selectFirst("a") ?: return@mapNotNull null + MangaTag( + title = a.text(), + key = a.attr("href"), + source = source + ) + }, + chapters = table.select("div.table.table-chapter-list").select("li.list-group-item.chapter_list").asReversed().mapIndexed { i, li -> + val a = li.select("a") + val href = a.attr("href").ifEmpty { + parseFailed("Link is missing") + } + MangaChapter( + id = generateUid(href), + name = a.select("label").text(), + number = i + 1, + url = href, + source = MangaSource.MANGAOWL + ) + } + ) + } + + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.withDomain() + val doc = loaderContext.httpGet(fullUrl).parseHtml() + val root = doc.body().select("div.item img.owl-lazy") ?: throw ParseException("Root not found") + return root.map { div -> + val url = div?.attr("abs:data-src") ?: parseFailed("Page image not found") + MangaPage( + id = generateUid(url), + url = url, + referer = fullUrl, + source = MangaSource.MANGAOWL + ) + } + } + + private fun parseStatus(status: String?) = when { + status == null -> null + status.contains("Ongoing") -> MangaState.ONGOING + status.contains("Completed") -> MangaState.FINISHED + else -> null + } + + override suspend fun getTags(): Set { + val doc = loaderContext.httpGet("https://${getDomain()}/").parseHtml() + val root = doc.body().select("ul.dropdown-menu.multi-column.columns-3").select("li") + return root.mapToSet { p -> + val a = p.selectFirst("a") ?: parseFailed("a is null") + MangaTag( + title = a.text().toCamelCase(), + key = a.attr("href"), + source = source + ) + } + } + + private fun getSortKey(sortOrder: SortOrder?) = + when (sortOrder ?: sortOrders.minByOrNull { it.ordinal }) { + SortOrder.POPULARITY -> "popular" + SortOrder.NEWEST -> "new_release" + SortOrder.UPDATED -> "lastest" + else -> "lastest" + } + + private fun getAlternativeSortKey(sortOrder: SortOrder?) = + when (sortOrder ?: sortOrders.minByOrNull { it.ordinal }) { + SortOrder.POPULARITY -> "0" + SortOrder.NEWEST -> "2" + SortOrder.UPDATED -> "3" + else -> "3" + } + +} \ No newline at end of file