diff --git a/src/all/fmreader/build.gradle b/src/all/fmreader/build.gradle index 3e27ec1fe..d9d075092 100644 --- a/src/all/fmreader/build.gradle +++ b/src/all/fmreader/build.gradle @@ -5,7 +5,7 @@ ext { appName = 'Tachiyomi: FMReader (multiple aggregators)' pkgNameSuffix = 'all.fmreader' extClass = '.FMReaderFactory' - extVersionCode = 3 + extVersionCode = 4 libVersion = '1.2' } diff --git a/src/all/fmreader/src/eu/kanade/tachiyomi/extension/all/fmreader/FMReader.kt b/src/all/fmreader/src/eu/kanade/tachiyomi/extension/all/fmreader/FMReader.kt index 39235d33a..c44b2af0a 100644 --- a/src/all/fmreader/src/eu/kanade/tachiyomi/extension/all/fmreader/FMReader.kt +++ b/src/all/fmreader/src/eu/kanade/tachiyomi/extension/all/fmreader/FMReader.kt @@ -38,8 +38,10 @@ abstract class FMReader( open val requestPath = "manga-list.html" + open val popularSort = "sort=views" + override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/$requestPath?listType=pagination&page=$page&sort=views&sort_type=DESC", headers) + GET("$baseUrl/$requestPath?listType=pagination&page=$page&$popularSort&sort_type=DESC", headers) override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val url = HttpUrl.parse("$baseUrl/$requestPath?")!!.newBuilder() @@ -79,21 +81,18 @@ abstract class FMReader( override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/$requestPath?listType=pagination&page=$page&sort=last_update&sort_type=DESC", headers) - // for sources that don't have the "page x of y" element - fun defaultMangaParse(response: Response): MangasPage = super.popularMangaParse(response) - override fun popularMangaParse(response: Response): MangasPage { val document = response.asJsoup() - val mangas = mutableListOf() - var hasNextPage = true - document.select(popularMangaSelector()).map { mangas.add(popularMangaFromElement(it)) } + val mangas = document.select(popularMangaSelector()).map { popularMangaFromElement(it) } // check if there's a next page - document.select(popularMangaNextPageSelector()).first().text().split(" ").let { - val currentPage = it[1] - val lastPage = it[3] - if (currentPage == lastPage) hasNextPage = false + val hasNextPage = (document.select(popularMangaNextPageSelector())?.first()?.text() ?: "").let { + if (it.contains(Regex("""\w*\s\d*\s\w*\s\d*"""))) { + it.split(" ").let { pageOf -> pageOf[1] != pageOf[3] } // current page not last page + } else { + it.isNotEmpty() // standard next page check + } } return MangasPage(mangas, hasNextPage) @@ -127,12 +126,15 @@ abstract class FMReader( return manga } - override fun latestUpdatesFromElement(element: Element): SManga = - popularMangaFromElement(element) + override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - // selects an element with text "x of y pages", must be first element if multiple elements are selected + /** + * can select one of 2 different types of elements + * one is an element with text "page x of y", must be the first element if it's part of a collection + * the other choice is the standard "next page" element (but most FMReader sources don't have this one) + */ override fun popularMangaNextPageSelector() = "div.col-lg-9 button.btn-info" override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() @@ -145,7 +147,7 @@ abstract class FMReader( manga.author = infoElement.select("li a.btn-info").text() manga.genre = infoElement.select("li a.btn-danger").joinToString { it.text() } - manga.status = parseStatus(infoElement.select("li a.btn-success").first().text().toLowerCase()) + manga.status = parseStatus(infoElement.select("li a.btn-success").first().text()) manga.description = document.select("div.row ~ div.row p").text().trim() manga.thumbnail_url = infoElement.select("img.thumbnail").attr("abs:src") @@ -153,7 +155,7 @@ abstract class FMReader( } // languages: en, vi, tr - fun parseStatus(element: String): Int = when (element) { + fun parseStatus(status: String): Int = when (status.toLowerCase()) { "completed", "complete", "incomplete", "đã hoàn thành", "tamamlandı" -> SManga.COMPLETED "ongoing", "on going", "updating", "chưa hoàn thành", "đang cập nhật", "devam ediyor" -> SManga.ONGOING else -> SManga.UNKNOWN @@ -231,13 +233,12 @@ abstract class FMReader( } } - override fun pageListParse(document: Document): List { - val pages = mutableListOf() + open val pageListImageSelector = "img.chapter-img" - document.select("img.chapter-img").forEachIndexed { i, img -> - pages.add(Page(i, "", img.attr("abs:data-src").let { if (it.isNotEmpty()) it else img.attr("abs:src") })) + override fun pageListParse(document: Document): List { + return document.select(pageListImageSelector).mapIndexed { i, img -> + Page(i, "", img.attr("abs:data-src").let { if (it.isNotEmpty()) it else img.attr("abs:src") }) } - return pages } override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") diff --git a/src/all/fmreader/src/eu/kanade/tachiyomi/extension/all/fmreader/FMReaderFactory.kt b/src/all/fmreader/src/eu/kanade/tachiyomi/extension/all/fmreader/FMReaderFactory.kt index aa0dda462..ff9994865 100644 --- a/src/all/fmreader/src/eu/kanade/tachiyomi/extension/all/fmreader/FMReaderFactory.kt +++ b/src/all/fmreader/src/eu/kanade/tachiyomi/extension/all/fmreader/FMReaderFactory.kt @@ -19,6 +19,7 @@ import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable +import java.net.URLEncoder class FMReaderFactory : SourceFactory { override fun createSources(): List = listOf( @@ -28,7 +29,6 @@ class FMReaderFactory : SourceFactory { MangaTiki(), MangaBone(), YoloManga(), - MangaLeer(), AiLoveManga(), ReadComicOnlineOrg(), MangaWeek(), @@ -40,7 +40,8 @@ class FMReaderFactory : SourceFactory { MangaTR(), Comicastle(), Manhwa18Net(), - Manhwa18NetRaw() + Manhwa18NetRaw(), + MangaBorn() ) } @@ -57,11 +58,6 @@ class YoloManga : FMReader("Yolo Manga", "https://yolomanga.ca", "es") { override fun chapterListSelector() = "div#tab-chapper ~ div#tab-chapper table tr" } -class MangaLeer : FMReader("MangaLeer", "https://mangaleer.com", "es") { - override val dateValueIndex = 1 - override val dateWordIndex = 2 -} - class AiLoveManga : FMReader("AiLoveManga", "https://ailovemanga.com", "vi") { override val requestPath = "danh-sach-truyen.html" // TODO: could add a genre search (different URL paths for genres) @@ -78,7 +74,7 @@ class AiLoveManga : FMReader("AiLoveManga", "https://ailovemanga.com", "vi") { manga.author = infoElement.select("a.btn-info").first().text() manga.artist = infoElement.select("a.btn-info + a").text() manga.genre = infoElement.select("a.btn-danger").joinToString { it.text() } - manga.status = parseStatus(infoElement.select("a.btn-success").text().toLowerCase()) + manga.status = parseStatus(infoElement.select("a.btn-success").text()) manga.description = document.select("div.col-sm-8 p").text().trim() manga.thumbnail_url = infoElement.select("img").attr("abs:src") @@ -97,11 +93,12 @@ class ReadComicOnlineOrg : FMReader("ReadComicOnline.org", "https://readcomiconl return if (response.headers("set-cookie").isNotEmpty()) { val body = FormBody.Builder() - .add("dqh_firewall", "%2F") + .add("dqh_firewall", URLEncoder.encode(request.url().toString().substringAfter(baseUrl), "utf-8")) .build() - val cookie = mutableListOf() - response.headers("set-cookie").map { cookie.add(it.substringBefore(" ")) } - headers.newBuilder().add("Cookie", cookie.joinToString { " " }).build() + val cookie = response.headers("set-cookie")[0].split(" ") + .filter {it.contains("__cfduid") || it.contains("PHPSESSID") } + .joinToString("; ") {it.substringBefore(";")} + headers.newBuilder().add("Cookie", cookie).build() client.newCall(POST(request.url().toString(), headers, body)).execute() } else { response @@ -110,10 +107,8 @@ class ReadComicOnlineOrg : FMReader("ReadComicOnline.org", "https://readcomiconl override val requestPath = "comic-list.html" override fun pageListParse(document: Document): List { - val pages = mutableListOf() - - document.select("div#divImage > select:first-of-type option").forEachIndexed { i, imgPage -> - pages.add(Page(i, imgPage.attr("value"), "")) + val pages = document.select("div#divImage > select:first-of-type option").mapIndexed { i, imgPage -> + Page(i, imgPage.attr("value")) } return pages.dropLast(1) // last page is a comments page } @@ -176,7 +171,7 @@ class MangaTR : FMReader("Manga-TR", "https://manga-tr.com", "tr") { manga.author = infoElement.select("table + table tr + tr td a").first()?.text() manga.artist = infoElement.select("table + table tr + tr td + td a").first()?.text() manga.genre = infoElement.select("div#tab1 table + table tr + tr td + td + td").text() - manga.status = parseStatus(infoElement.select("div#tab1 table tr + tr td a").first().text().toLowerCase()) + manga.status = parseStatus(infoElement.select("div#tab1 table tr + tr td a").first().text()) manga.description = infoElement.select("div.well").text().trim() manga.thumbnail_url = document.select("img.thumbnail").attr("abs:src") @@ -227,7 +222,7 @@ class MangaTR : FMReader("Manga-TR", "https://manga-tr.com", "tr") { val pages = mutableListOf() document.select("div.chapter-content select:first-of-type option").forEachIndexed { i, imgPage -> - pages.add(Page(i, "$baseUrl/${imgPage.attr("value")}", "")) + pages.add(Page(i, "$baseUrl/${imgPage.attr("value")}")) } return pages.dropLast(1) // last page is a comments page } @@ -236,13 +231,13 @@ class MangaTR : FMReader("Manga-TR", "https://manga-tr.com", "tr") { } class Comicastle : FMReader("Comicastle", "https://www.comicastle.org", "en") { - override val requestPath = "comic-dir" - // this source doesn't have the "page x of y" element - override fun popularMangaNextPageSelector() = "li:contains(»)" - - override fun popularMangaParse(response: Response) = defaultMangaParse(response) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/comic-dir?q=$query", headers) - override fun searchMangaParse(response: Response): MangasPage = defaultMangaParse(response) + override fun popularMangaRequest(page: Int): Request = + GET("$baseUrl/comic-dir?sorting=views&c-page=$page&sorting-type=DESC", headers) + override fun popularMangaNextPageSelector() = "li:contains(»):not(.disabled)" + override fun latestUpdatesRequest(page: Int): Request = + GET("$baseUrl/comic-dir?sorting=lastUpdate&c-page=$page&sorting-type=ASC", headers) + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = + GET("$baseUrl/comic-dir?q=$query" + if (page > 1) "&c-page=$page" else "", headers) override fun getFilterList() = FilterList() override fun mangaDetailsParse(document: Document): SManga { val manga = SManga.create() @@ -263,7 +258,7 @@ class Comicastle : FMReader("Comicastle", "https://www.comicastle.org", "en") { val pages = mutableListOf() document.select("div.text-center select option").forEachIndexed { i, imgPage -> - pages.add(Page(i, imgPage.attr("value"), "")) + pages.add(Page(i, imgPage.attr("value"))) } return pages } @@ -296,3 +291,42 @@ class Manhwa18NetRaw : FMReader("Manhwa18.net Raw", "https://manhwa18.net", "ko" override fun getFilterList() = FilterList(super.getFilterList().filterNot { it == GenreList(getGenreList()) }) } + +class MangaBorn : FMReader("MangaBorn", "http://hellxlight.com", "en") { + override val requestPath = "manga_list" + override val popularSort = "type=topview" + override fun popularMangaNextPageSelector() = "div.page-number a.select + a:not(.go-p-end)" + override fun popularMangaSelector() = "div.story-item" + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + return GET("$baseUrl/search/${query.replace(" ", "_")}?page=$page", headers) + } + override fun searchMangaParse(response: Response): MangasPage { + return response.asJsoup().let { document -> + val mangas = document.select(searchMangaSelector()).map { searchMangaFromElement(it) } + MangasPage(mangas, document.select(searchMangaNextPageSelector()).isNotEmpty()) + } + } + override fun searchMangaFromElement(element: Element): SManga { + return SManga.create().apply { + element.select("h2 a").let { + setUrlWithoutDomain(it.attr("href")) + title = it.text() + } + thumbnail_url = element.select("img").attr("abs:src") + } + } + override fun mangaDetailsParse(document: Document): SManga { + return SManga.create().apply { + document.select("div.story_content").let { info -> + author = info.select("span:contains(Author) + a").text() + genre = info.select("span:contains(Genres) + a").joinToString { it.text() } + status = parseStatus(info.select("span:contains(Status) + a").text()) + thumbnail_url = info.select("img.avatar").attr("abs:src") + description = info.select("div#story_discription > p").text() + } + } + } + override fun chapterListSelector() = "div.chapter_list li" + override val pageListImageSelector = "div.panel-read-story img" + override fun getFilterList() = FilterList() +}