FMReader update (#1892)

FMReader update
This commit is contained in:
Mike 2019-12-22 11:09:21 -05:00 committed by arkon
parent 7bf2d4b256
commit 42a5f6585f
3 changed files with 83 additions and 48 deletions

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: FMReader (multiple aggregators)' appName = 'Tachiyomi: FMReader (multiple aggregators)'
pkgNameSuffix = 'all.fmreader' pkgNameSuffix = 'all.fmreader'
extClass = '.FMReaderFactory' extClass = '.FMReaderFactory'
extVersionCode = 3 extVersionCode = 4
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -38,8 +38,10 @@ abstract class FMReader(
open val requestPath = "manga-list.html" open val requestPath = "manga-list.html"
open val popularSort = "sort=views"
override fun popularMangaRequest(page: Int): Request = 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 { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = HttpUrl.parse("$baseUrl/$requestPath?")!!.newBuilder() val url = HttpUrl.parse("$baseUrl/$requestPath?")!!.newBuilder()
@ -79,21 +81,18 @@ abstract class FMReader(
override fun latestUpdatesRequest(page: Int): Request = override fun latestUpdatesRequest(page: Int): Request =
GET("$baseUrl/$requestPath?listType=pagination&page=$page&sort=last_update&sort_type=DESC", headers) 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 { override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup() val document = response.asJsoup()
val mangas = mutableListOf<SManga>()
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 // check if there's a next page
document.select(popularMangaNextPageSelector()).first().text().split(" ").let { val hasNextPage = (document.select(popularMangaNextPageSelector())?.first()?.text() ?: "").let {
val currentPage = it[1] if (it.contains(Regex("""\w*\s\d*\s\w*\s\d*"""))) {
val lastPage = it[3] it.split(" ").let { pageOf -> pageOf[1] != pageOf[3] } // current page not last page
if (currentPage == lastPage) hasNextPage = false } else {
it.isNotEmpty() // standard next page check
}
} }
return MangasPage(mangas, hasNextPage) return MangasPage(mangas, hasNextPage)
@ -127,12 +126,15 @@ abstract class FMReader(
return manga return manga
} }
override fun latestUpdatesFromElement(element: Element): SManga = override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
popularMangaFromElement(element)
override fun searchMangaFromElement(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 popularMangaNextPageSelector() = "div.col-lg-9 button.btn-info"
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
@ -145,7 +147,7 @@ abstract class FMReader(
manga.author = infoElement.select("li a.btn-info").text() manga.author = infoElement.select("li a.btn-info").text()
manga.genre = infoElement.select("li a.btn-danger").joinToString { it.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.description = document.select("div.row ~ div.row p").text().trim()
manga.thumbnail_url = infoElement.select("img.thumbnail").attr("abs:src") manga.thumbnail_url = infoElement.select("img.thumbnail").attr("abs:src")
@ -153,7 +155,7 @@ abstract class FMReader(
} }
// languages: en, vi, tr // 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 "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 "ongoing", "on going", "updating", "chưa hoàn thành", "đang cập nhật", "devam ediyor" -> SManga.ONGOING
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
@ -231,13 +233,12 @@ abstract class FMReader(
} }
} }
override fun pageListParse(document: Document): List<Page> { open val pageListImageSelector = "img.chapter-img"
val pages = mutableListOf<Page>()
document.select("img.chapter-img").forEachIndexed { i, img -> override fun pageListParse(document: Document): List<Page> {
pages.add(Page(i, "", img.attr("abs:data-src").let { if (it.isNotEmpty()) it else img.attr("abs:src") })) 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") override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")

View File

@ -19,6 +19,7 @@ import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
import java.net.URLEncoder
class FMReaderFactory : SourceFactory { class FMReaderFactory : SourceFactory {
override fun createSources(): List<Source> = listOf( override fun createSources(): List<Source> = listOf(
@ -28,7 +29,6 @@ class FMReaderFactory : SourceFactory {
MangaTiki(), MangaTiki(),
MangaBone(), MangaBone(),
YoloManga(), YoloManga(),
MangaLeer(),
AiLoveManga(), AiLoveManga(),
ReadComicOnlineOrg(), ReadComicOnlineOrg(),
MangaWeek(), MangaWeek(),
@ -40,7 +40,8 @@ class FMReaderFactory : SourceFactory {
MangaTR(), MangaTR(),
Comicastle(), Comicastle(),
Manhwa18Net(), 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" 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") { class AiLoveManga : FMReader("AiLoveManga", "https://ailovemanga.com", "vi") {
override val requestPath = "danh-sach-truyen.html" override val requestPath = "danh-sach-truyen.html"
// TODO: could add a genre search (different URL paths for genres) // 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.author = infoElement.select("a.btn-info").first().text()
manga.artist = infoElement.select("a.btn-info + a").text() manga.artist = infoElement.select("a.btn-info + a").text()
manga.genre = infoElement.select("a.btn-danger").joinToString { it.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.description = document.select("div.col-sm-8 p").text().trim()
manga.thumbnail_url = infoElement.select("img").attr("abs:src") 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()) { return if (response.headers("set-cookie").isNotEmpty()) {
val body = FormBody.Builder() val body = FormBody.Builder()
.add("dqh_firewall", "%2F") .add("dqh_firewall", URLEncoder.encode(request.url().toString().substringAfter(baseUrl), "utf-8"))
.build() .build()
val cookie = mutableListOf<String>() val cookie = response.headers("set-cookie")[0].split(" ")
response.headers("set-cookie").map { cookie.add(it.substringBefore(" ")) } .filter {it.contains("__cfduid") || it.contains("PHPSESSID") }
headers.newBuilder().add("Cookie", cookie.joinToString { " " }).build() .joinToString("; ") {it.substringBefore(";")}
headers.newBuilder().add("Cookie", cookie).build()
client.newCall(POST(request.url().toString(), headers, body)).execute() client.newCall(POST(request.url().toString(), headers, body)).execute()
} else { } else {
response response
@ -110,10 +107,8 @@ class ReadComicOnlineOrg : FMReader("ReadComicOnline.org", "https://readcomiconl
override val requestPath = "comic-list.html" override val requestPath = "comic-list.html"
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>() val pages = document.select("div#divImage > select:first-of-type option").mapIndexed { i, imgPage ->
Page(i, imgPage.attr("value"))
document.select("div#divImage > select:first-of-type option").forEachIndexed { i, imgPage ->
pages.add(Page(i, imgPage.attr("value"), ""))
} }
return pages.dropLast(1) // last page is a comments page 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.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.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.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.description = infoElement.select("div.well").text().trim()
manga.thumbnail_url = document.select("img.thumbnail").attr("abs:src") 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<Page>() val pages = mutableListOf<Page>()
document.select("div.chapter-content select:first-of-type option").forEachIndexed { i, imgPage -> 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 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") { class Comicastle : FMReader("Comicastle", "https://www.comicastle.org", "en") {
override val requestPath = "comic-dir" override fun popularMangaRequest(page: Int): Request =
// this source doesn't have the "page x of y" element GET("$baseUrl/comic-dir?sorting=views&c-page=$page&sorting-type=DESC", headers)
override fun popularMangaNextPageSelector() = "li:contains(»)" override fun popularMangaNextPageSelector() = "li:contains(»):not(.disabled)"
override fun latestUpdatesRequest(page: Int): Request =
override fun popularMangaParse(response: Response) = defaultMangaParse(response) 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", headers) override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request =
override fun searchMangaParse(response: Response): MangasPage = defaultMangaParse(response) GET("$baseUrl/comic-dir?q=$query" + if (page > 1) "&c-page=$page" else "", headers)
override fun getFilterList() = FilterList() override fun getFilterList() = FilterList()
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val manga = SManga.create() val manga = SManga.create()
@ -263,7 +258,7 @@ class Comicastle : FMReader("Comicastle", "https://www.comicastle.org", "en") {
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
document.select("div.text-center select option").forEachIndexed { i, imgPage -> 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 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()) }) 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()
}