parent
7bf2d4b256
commit
42a5f6585f
|
@ -5,7 +5,7 @@ ext {
|
|||
appName = 'Tachiyomi: FMReader (multiple aggregators)'
|
||||
pkgNameSuffix = 'all.fmreader'
|
||||
extClass = '.FMReaderFactory'
|
||||
extVersionCode = 3
|
||||
extVersionCode = 4
|
||||
libVersion = '1.2'
|
||||
}
|
||||
|
||||
|
|
|
@ -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<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
|
||||
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<Page> {
|
||||
val pages = mutableListOf<Page>()
|
||||
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<Page> {
|
||||
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")
|
||||
|
|
|
@ -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<Source> = 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<String>()
|
||||
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<Page> {
|
||||
val pages = mutableListOf<Page>()
|
||||
|
||||
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<Page>()
|
||||
|
||||
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<Page>()
|
||||
|
||||
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()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue