diff --git a/src/en/heavenmanga/build.gradle b/src/en/heavenmanga/build.gradle index 5f0e03c8a..233fb41c1 100644 --- a/src/en/heavenmanga/build.gradle +++ b/src/en/heavenmanga/build.gradle @@ -5,7 +5,7 @@ ext { appName = 'Tachiyomi: Heaven Manga' pkgNameSuffix = 'en.heavenmanga' extClass = '.Heavenmanga' - extVersionCode = 1 + extVersionCode = 2 libVersion = '1.2' } diff --git a/src/en/heavenmanga/src/eu/kanade/tachiyomi/extension/en/heavenmanga/Heavenmanga.kt b/src/en/heavenmanga/src/eu/kanade/tachiyomi/extension/en/heavenmanga/Heavenmanga.kt index 7a5938d15..ced3f95c6 100644 --- a/src/en/heavenmanga/src/eu/kanade/tachiyomi/extension/en/heavenmanga/Heavenmanga.kt +++ b/src/en/heavenmanga/src/eu/kanade/tachiyomi/extension/en/heavenmanga/Heavenmanga.kt @@ -3,7 +3,10 @@ package eu.kanade.tachiyomi.extension.en.heavenmanga import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.* import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.OkHttpClient import okhttp3.Request +import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element import java.text.SimpleDateFormat @@ -20,8 +23,25 @@ class Heavenmanga : ParsedHttpSource() { override val supportsLatest = true + override val client: OkHttpClient = network.cloudflareClient + + // selectors override fun popularMangaSelector() = "div.comics-grid div.entry" + override fun latestUpdatesSelector() = popularMangaSelector() + + override fun searchMangaSelector() = popularMangaSelector() + + override fun chapterListSelector() = "div.chapters-wrapper div.two-rows a" + + override fun popularMangaNextPageSelector() = "a.next[href^=http:]:has(i.fa-angle-right)" + + override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() + + override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() + + + // popular override fun popularMangaRequest(page: Int): Request { if (page == 1) { return GET("$baseUrl/manga-list/") @@ -30,16 +50,6 @@ class Heavenmanga : ParsedHttpSource() { } } - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesRequest(page: Int): Request { - if (page == 1) { - return GET("$baseUrl/latest-update/") - } else { - return GET("$baseUrl/latest-update/page-$page") - } - } - override fun popularMangaFromElement(element: Element): SManga { val manga = SManga.create() @@ -50,23 +60,59 @@ class Heavenmanga : ParsedHttpSource() { return manga } + + + // latest + override fun latestUpdatesRequest(page: Int): Request { + if (page == 1) { + return GET("$baseUrl/latest-update/") + } else { + return GET("$baseUrl/latest-update/page-$page") + } + } + override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - override fun popularMangaNextPageSelector() = "div.pagination > a.next" - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() + // search override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = "$baseUrl/?s=$query" + val url = if (query.isNotBlank()) { + "$baseUrl/?s=$query" + } else { + var ret = String() + var genre:String + var name:String + var authorFilter: String? = null + filters.forEach { filter -> + when (filter) { + is TextField -> { + if (filter.key == "author" && !filter.state.isEmpty()) { + authorFilter = filter.state + if (!authorFilter.isNullOrEmpty()) { + ret = "$baseUrl/author/$authorFilter/page-$page" + } + } + + } + is GenreFilter -> { + if(filter.toUriPart().isNotBlank() && filter.state != 0) { + name = filter.toUriPart() + genre = if(name == "completed") "completed" else "genre/$name" + ret = "$baseUrl/$genre/page-$page" + } + } + } + } + ret + } + return GET(url, headers) } - override fun searchMangaSelector() = popularMangaSelector() - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - + // details override fun mangaDetailsRequest(manga: SManga): Request { if (manga.url.startsWith("http")) { return GET(manga.url, headers) @@ -98,6 +144,7 @@ class Heavenmanga : ParsedHttpSource() { else -> SManga.UNKNOWN } + // chapter override fun chapterListRequest(manga: SManga): Request { if (manga.url.startsWith("http")) { return GET(manga.url, headers) @@ -105,17 +152,39 @@ class Heavenmanga : ParsedHttpSource() { return super.chapterListRequest(manga) } - override fun chapterListSelector() = "div.chapters-wrapper div.two-rows" + // copied from en-holymanga extension + override fun chapterListParse(response: Response): List { + val allChapters = mutableListOf() + val paginationSelector = latestUpdatesNextPageSelector() + var document = response.asJsoup() + var dateIndex = 0 + var continueParsing = true + + // Chapter list is paginated + while (continueParsing) { + // Chapter titles and urls + document.select(chapterListSelector()).map{allChapters.add(chapterFromElement(it))} + // Chapter dates + document.select("div.chapter-date").forEach { + allChapters[dateIndex].date_upload = parseDate(it.text()) + dateIndex++ + } + // Next page of chapters + if (document.select(paginationSelector).isNotEmpty()) { + document = client.newCall(GET(document.select(paginationSelector) + .attr("href"), headers)).execute().asJsoup() + } else { + continueParsing = false + } + } + + return allChapters + } override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - val chapter = SChapter.create() - chapter.url = urlElement.attr("href") - chapter.name = element.select("div.r1").text() - - val date = element.select("div.chapter-date").text() - if (!date.isNullOrEmpty()) chapter.date_upload = parseDate(date) + chapter.setUrlWithoutDomain(element.attr("href")) + chapter.name = element.text() return chapter } @@ -123,6 +192,7 @@ class Heavenmanga : ParsedHttpSource() { return SimpleDateFormat("MM/dd/yyyy", Locale.US).parse(date).time } + // pages override fun pageListRequest(chapter: SChapter): Request { if (chapter.url.startsWith("http")) { return GET(chapter.url, headers) @@ -144,6 +214,77 @@ class Heavenmanga : ParsedHttpSource() { override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - override fun getFilterList() = FilterList() + // filters + private class TextField(name: String, val key: String) : Filter.Text(name) + override fun getFilterList() = FilterList( + Filter.Header("Filters cannot be used while searching."), + Filter.Separator(), + Filter.Header("An invalid author will return mangas from all authors."), + Filter.Separator("-----------------"), + TextField("Author", "author"), + GenreFilter () + ) + + // [...document.querySelectorAll('.sub-menu li a')].map(a => `Pair("${a.textContent}", "${a.getAttribute('href')}")`).join(',\n') + // from $baseUrl + private class GenreFilter: UriPartFilter("Genres", + arrayOf( + Pair("Choose a genre", ""), + Pair("Action", "action"), + Pair("Adult", "adult"), + Pair("Anime", "anime"), + Pair("Adventure", "adventure"), + Pair("Comedy", "comedy"), + Pair("Comic", "comic"), + Pair("Completed", "completed"), + Pair("Cooking", "cooking"), + Pair("Doraemon", "doraemon"), + Pair("Doujinshi", "doujinshi"), + Pair("Drama", "drama"), + Pair("Ecchi", "ecchi"), + Pair("Fantasy", "fantasy"), + Pair("Full Color", "full-color"), + Pair("Gender Bender", "gender-bender"), + Pair("Harem", "harem"), + Pair("Historical", "historical"), + Pair("Horror", "horror"), + Pair("Josei", "josei"), + Pair("Live action", "live-action"), + Pair("Magic", "magic"), + Pair("Manga", "manga"), + Pair("Manhua", "manhua"), + Pair("Manhwa", "manhwa"), + Pair("Martial Arts", "martial-arts"), + Pair("Mature", "mature"), + Pair("Mecha", "mecha"), + Pair("Mystery", "mystery"), + Pair("One shot", "one-shot"), + Pair("Psychological", "psychological"), + Pair("Romance", "romance"), + Pair("School Life", "school-life"), + Pair("Sci-fi", "sci-fi"), + Pair("Seinen", "seinen"), + Pair("Shoujo", "shoujo"), + Pair("Shoujo Ai", "shoujo-ai"), + Pair("Shounen", "shounen"), + Pair("Shounen Ai", "shounen-ai"), + Pair("Slice of life", "slice-of-life"), + Pair("Smut", "smut"), + Pair("Yaoi", "yaoi"), + Pair("Yuri", "yuri"), + Pair("Sports", "sports"), + Pair("Supernatural", "supernatural"), + Pair("Tragedy", "tragedy"), + Pair("Trap", "trap"), + Pair("Webtoons", "webtoons") + ) + ) + + + + private open class UriPartFilter(displayName: String, val vals: Array>) : + Filter.Select(displayName, vals.map { it.first }.toTypedArray()) { + fun toUriPart() = vals[state].second + } }