Implement latest updates. (#472)

This commit is contained in:
Roman Martinez 2016-09-30 12:11:51 -07:00 committed by inorichi
parent 09a8a494a0
commit 0b3dda18d3
27 changed files with 482 additions and 103 deletions

View File

@ -53,6 +53,11 @@ abstract class OnlineSource(context: Context) : Source {
*/ */
abstract val lang: Language abstract val lang: Language
/**
* If the Source has Support for Latest Updates
*/
abstract val supportsLatest : Boolean
/** /**
* Headers used for requests. * Headers used for requests.
*/ */
@ -96,6 +101,17 @@ abstract class OnlineSource(context: Context) : Source {
page page
} }
/**
* Returns an observable containing a page with a list of latest manga.
*/
open fun fetchLatestUpdates(page: MangasPage): Observable<MangasPage> = client
.newCall(latestUpdatesRequest(page))
.asObservable()
.map { response ->
latestUpdatesParse(response, page)
page
}
/** /**
* Returns the request for the popular manga given the page. Override only if it's needed to * Returns the request for the popular manga given the page. Override only if it's needed to
* send different headers or request method like POST. * send different headers or request method like POST.
@ -109,11 +125,26 @@ abstract class OnlineSource(context: Context) : Source {
return GET(page.url, headers) return GET(page.url, headers)
} }
/**
* Returns the request for latest manga given the page.
*/
open protected fun latestUpdatesRequest(page: MangasPage): Request {
if (page.page == 1) {
page.url = latestUpdatesInitialUrl()
}
return GET(page.url, headers)
}
/** /**
* Returns the absolute url of the first page to popular manga. * Returns the absolute url of the first page to popular manga.
*/ */
abstract protected fun popularMangaInitialUrl(): String abstract protected fun popularMangaInitialUrl(): String
/**
* Returns the absolute url of the first page to latest manga.
*/
abstract protected fun latestUpdatesInitialUrl(): String
/** /**
* Parse the response from the site. It should add a list of manga and the absolute url to the * Parse the response from the site. It should add a list of manga and the absolute url to the
* next page (if it has a next one) to [page]. * next page (if it has a next one) to [page].
@ -123,6 +154,11 @@ abstract class OnlineSource(context: Context) : Source {
*/ */
abstract protected fun popularMangaParse(response: Response, page: MangasPage) abstract protected fun popularMangaParse(response: Response, page: MangasPage)
/**
* Same as [popularMangaParse], but for latest manga.
*/
abstract protected fun latestUpdatesParse(response: Response, page: MangasPage)
/** /**
* Returns an observable containing a page with a list of manga. Normally it's not needed to * Returns an observable containing a page with a list of manga. Normally it's not needed to
* override this method. * override this method.

View File

@ -37,11 +37,33 @@ abstract class ParsedOnlineSource(context: Context) : OnlineSource(context) {
} }
} }
/**
* Parse the response from the site for latest updates and fills [page].
*/
override fun latestUpdatesParse(response: Response, page: MangasPage) {
val document = response.asJsoup()
for (element in document.select(latestUpdatesSelector())) {
Manga.create(id).apply {
latestUpdatesFromElement(element, this)
page.mangas.add(this)
}
}
latestUpdatesNextPageSelector()?.let { selector ->
page.nextPageUrl = document.select(selector).first()?.absUrl("href")
}
}
/** /**
* Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga.
*/ */
abstract protected fun popularMangaSelector(): String abstract protected fun popularMangaSelector(): String
/**
* Returns the Jsoup selector similar to [popularMangaSelector], but for latest updates.
*/
abstract protected fun latestUpdatesSelector(): String
/** /**
* Fills [manga] with the given [element]. Most sites only show the title and the url, it's * Fills [manga] with the given [element]. Most sites only show the title and the url, it's
* totally safe to fill only those two values. * totally safe to fill only those two values.
@ -51,12 +73,22 @@ abstract class ParsedOnlineSource(context: Context) : OnlineSource(context) {
*/ */
abstract protected fun popularMangaFromElement(element: Element, manga: Manga) abstract protected fun popularMangaFromElement(element: Element, manga: Manga)
/**
* Fills [manga] with the given [element]. For latest updates.
*/
abstract protected fun latestUpdatesFromElement(element: Element, manga: Manga)
/** /**
* Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if * Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if
* there's no next page. * there's no next page.
*/ */
abstract protected fun popularMangaNextPageSelector(): String? abstract protected fun popularMangaNextPageSelector(): String?
/**
* Returns the Jsoup selector that returns the <a> tag, like [popularMangaNextPageSelector].
*/
abstract protected fun latestUpdatesNextPageSelector(): String?
/** /**
* Parse the response from the site and fills [page]. * Parse the response from the site and fills [page].
* *

View File

@ -34,6 +34,8 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con
getLanguages().find { code == it.code }!! getLanguages().find { code == it.code }!!
} }
override val supportsLatest = map.supportsLatest.toBoolean()
override val client = when(map.client) { override val client = when(map.client) {
"cloudflare" -> network.cloudflareClient "cloudflare" -> network.cloudflareClient
else -> network.client else -> network.client
@ -53,8 +55,20 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con
} }
} }
override fun latestUpdatesRequest(page: MangasPage): Request {
if (page.page == 1) {
page.url = latestUpdatesInitialUrl()
}
return when (map.latestupdates.method?.toLowerCase()) {
"post" -> POST(page.url, headers, map.latestupdates.createForm())
else -> GET(page.url, headers)
}
}
override fun popularMangaInitialUrl() = map.popular.url override fun popularMangaInitialUrl() = map.popular.url
override fun latestUpdatesInitialUrl() = map.latestupdates.url
override fun popularMangaParse(response: Response, page: MangasPage) { override fun popularMangaParse(response: Response, page: MangasPage) {
val document = response.asJsoup() val document = response.asJsoup()
for (element in document.select(map.popular.manga_css)) { for (element in document.select(map.popular.manga_css)) {
@ -70,6 +84,21 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con
} }
} }
override fun latestUpdatesParse(response: Response, page: MangasPage) {
val document = response.asJsoup()
for (element in document.select(map.latestupdates.manga_css)) {
Manga.create(id).apply {
title = element.text()
setUrlWithoutDomain(element.attr("href"))
page.mangas.add(this)
}
}
map.popular.next_url_css?.let { selector ->
page.nextPageUrl = document.select(selector).first()?.absUrl("href")
}
}
override fun searchMangaRequest(page: MangasPage, query: String, filters: List<Filter>): Request { override fun searchMangaRequest(page: MangasPage, query: String, filters: List<Filter>): Request {
if (page.page == 1) { if (page.page == 1) {
page.url = searchMangaInitialUrl(query, filters) page.url = searchMangaInitialUrl(query, filters)

View File

@ -25,11 +25,15 @@ class YamlSourceNode(uncheckedMap: Map<*, *>) {
val lang: String by map val lang: String by map
val supportsLatest: String by map
val client: String? val client: String?
get() = map["client"] as? String get() = map["client"] as? String
val popular = PopularNode(toMap(map["popular"])!!) val popular = PopularNode(toMap(map["popular"])!!)
val latestupdates = LatestUpdatesNode(toMap(map["latest_updates"])!!)
val search = SearchNode(toMap(map["search"])!!) val search = SearchNode(toMap(map["search"])!!)
val manga = MangaNode(toMap(map["manga"])!!) val manga = MangaNode(toMap(map["manga"])!!)
@ -73,6 +77,17 @@ class PopularNode(override val map: Map<String, Any?>): RequestableNode {
} }
class LatestUpdatesNode(override val map: Map<String, Any?>): RequestableNode {
val manga_css: String by map
val next_url_css: String?
get() = map["next_url_css"] as? String
}
class SearchNode(override val map: Map<String, Any?>): RequestableNode { class SearchNode(override val map: Map<String, Any?>): RequestableNode {
val manga_css: String by map val manga_css: String by map

View File

@ -36,6 +36,8 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex
override val lang: Language get() = EN override val lang: Language get() = EN
override val supportsLatest = true
private val datePattern = Pattern.compile("(\\d+|A|An)\\s+(.*?)s? ago.*") private val datePattern = Pattern.compile("(\\d+|A|An)\\s+(.*?)s? ago.*")
private val dateFields = HashMap<String, Int>().apply { private val dateFields = HashMap<String, Int>().apply {
@ -59,6 +61,8 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex
override fun popularMangaInitialUrl() = "$baseUrl/search_ajax?order_cond=views&order=desc&p=1" override fun popularMangaInitialUrl() = "$baseUrl/search_ajax?order_cond=views&order=desc&p=1"
override fun latestUpdatesInitialUrl() = "$baseUrl/search_ajax?order_cond=update&order=desc&p=1"
override fun popularMangaParse(response: Response, page: MangasPage) { override fun popularMangaParse(response: Response, page: MangasPage) {
val document = response.asJsoup() val document = response.asJsoup()
for (element in document.select(popularMangaSelector())) { for (element in document.select(popularMangaSelector())) {
@ -73,8 +77,24 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex
} }
} }
override fun latestUpdatesParse(response: Response, page: MangasPage) {
val document = response.asJsoup()
for (element in document.select(latestUpdatesSelector())) {
Manga.create(id).apply {
latestUpdatesFromElement(element, this)
page.mangas.add(this)
}
}
page.nextPageUrl = document.select(latestUpdatesNextPageSelector()).first()?.let {
"$baseUrl/search_ajax?order_cond=update&order=desc&p=${page.page + 1}"
}
}
override fun popularMangaSelector() = "tr:has(a)" override fun popularMangaSelector() = "tr:has(a)"
override fun latestUpdatesSelector() = "tr:has(a)"
override fun popularMangaFromElement(element: Element, manga: Manga) { override fun popularMangaFromElement(element: Element, manga: Manga) {
element.select("a[href^=http://bato.to]").first().let { element.select("a[href^=http://bato.to]").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
@ -82,8 +102,14 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex
} }
} }
override fun latestUpdatesFromElement(element: Element, manga: Manga) {
popularMangaFromElement(element, manga)
}
override fun popularMangaNextPageSelector() = "#show_more_row" override fun popularMangaNextPageSelector() = "#show_more_row"
override fun latestUpdatesNextPageSelector() = "#show_more_row"
override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = "$baseUrl/search_ajax?name=${Uri.encode(query)}&order_cond=views&order=desc&p=1&genre_cond=and&genres=${getFilterParams(filters)}" override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = "$baseUrl/search_ajax?name=${Uri.encode(query)}&order_cond=views&order=desc&p=1&genre_cond=and&genres=${getFilterParams(filters)}"
private fun getFilterParams(filters: List<Filter>): String = filters private fun getFilterParams(filters: List<Filter>): String = filters
@ -320,4 +346,5 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex
Filter("29", "Yaoi"), Filter("29", "Yaoi"),
Filter("31", "Yuri") Filter("31", "Yuri")
) )
} }

View File

@ -27,12 +27,18 @@ class Kissmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
override val lang: Language get() = EN override val lang: Language get() = EN
override val client: OkHttpClient get() = network.cloudflareClient override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient
override fun popularMangaInitialUrl() = "$baseUrl/MangaList/MostPopular" override fun popularMangaInitialUrl() = "$baseUrl/MangaList/MostPopular"
override fun latestUpdatesInitialUrl() = "http://kissmanga.com/MangaList/LatestUpdate"
override fun popularMangaSelector() = "table.listing tr:gt(1)" override fun popularMangaSelector() = "table.listing tr:gt(1)"
override fun latestUpdatesSelector() = "table.listing tr:gt(1)"
override fun popularMangaFromElement(element: Element, manga: Manga) { override fun popularMangaFromElement(element: Element, manga: Manga) {
element.select("td a:eq(0)").first().let { element.select("td a:eq(0)").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
@ -40,8 +46,14 @@ class Kissmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
} }
} }
override fun latestUpdatesFromElement(element: Element, manga: Manga) {
popularMangaFromElement(element, manga)
}
override fun popularMangaNextPageSelector() = "li > a:contains( Next)" override fun popularMangaNextPageSelector() = "li > a:contains( Next)"
override fun latestUpdatesNextPageSelector(): String = "ul.pager > li > a:contains(Next)"
override fun searchMangaRequest(page: MangasPage, query: String, filters: List<Filter>): Request { override fun searchMangaRequest(page: MangasPage, query: String, filters: List<Filter>): Request {
if (page.page == 1) { if (page.page == 1) {
page.url = searchMangaInitialUrl(query, filters) page.url = searchMangaInitialUrl(query, filters)

View File

@ -23,10 +23,16 @@ class Mangafox(context: Context, override val id: Int) : ParsedOnlineSource(cont
override val lang: Language get() = EN override val lang: Language get() = EN
override val supportsLatest = true
override fun popularMangaInitialUrl() = "$baseUrl/directory/" override fun popularMangaInitialUrl() = "$baseUrl/directory/"
override fun latestUpdatesInitialUrl() = "$baseUrl/directory/?latest"
override fun popularMangaSelector() = "div#mangalist > ul.list > li" override fun popularMangaSelector() = "div#mangalist > ul.list > li"
override fun latestUpdatesSelector() = "div#mangalist > ul.list > li"
override fun popularMangaFromElement(element: Element, manga: Manga) { override fun popularMangaFromElement(element: Element, manga: Manga) {
element.select("a.title").first().let { element.select("a.title").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
@ -34,8 +40,14 @@ class Mangafox(context: Context, override val id: Int) : ParsedOnlineSource(cont
} }
} }
override fun latestUpdatesFromElement(element: Element, manga: Manga) {
popularMangaFromElement(element, manga)
}
override fun popularMangaNextPageSelector() = "a:has(span.next)" override fun popularMangaNextPageSelector() = "a:has(span.next)"
override fun latestUpdatesNextPageSelector() = "a:has(span.next)"
override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = override fun searchMangaInitialUrl(query: String, filters: List<Filter>) =
"$baseUrl/search.php?name_method=cw&advopts=1&order=za&sort=views&name=$query&page=1&${filters.map { it.id + "=1" }.joinToString("&")}" "$baseUrl/search.php?name_method=cw&advopts=1&order=za&sort=views&name=$query&page=1&${filters.map { it.id + "=1" }.joinToString("&")}"
@ -157,4 +169,5 @@ class Mangafox(context: Context, override val id: Int) : ParsedOnlineSource(cont
Filter("genres[Yaoi]", "Yaoi"), Filter("genres[Yaoi]", "Yaoi"),
Filter("genres[Yuri]", "Yuri") Filter("genres[Yuri]", "Yuri")
) )
} }

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.data.source.online.english package eu.kanade.tachiyomi.data.source.online.english
import android.content.Context import android.content.Context
import android.util.Log
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.source.EN import eu.kanade.tachiyomi.data.source.EN
@ -22,10 +21,16 @@ class Mangahere(context: Context, override val id: Int) : ParsedOnlineSource(con
override val lang: Language get() = EN override val lang: Language get() = EN
override fun popularMangaInitialUrl() = "$baseUrl/directory/" override val supportsLatest = true
override fun popularMangaInitialUrl() = "$baseUrl/directory/?views.za"
override fun latestUpdatesInitialUrl() = "$baseUrl/directory/?last_chapter_time.za"
override fun popularMangaSelector() = "div.directory_list > ul > li" override fun popularMangaSelector() = "div.directory_list > ul > li"
override fun latestUpdatesSelector() = "div.directory_list > ul > li"
override fun popularMangaFromElement(element: Element, manga: Manga) { override fun popularMangaFromElement(element: Element, manga: Manga) {
element.select("div.title > a").first().let { element.select("div.title > a").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
@ -33,8 +38,14 @@ class Mangahere(context: Context, override val id: Int) : ParsedOnlineSource(con
} }
} }
override fun latestUpdatesFromElement(element: Element, manga: Manga) {
popularMangaFromElement(element, manga)
}
override fun popularMangaNextPageSelector() = "div.next-page > a.next" override fun popularMangaNextPageSelector() = "div.next-page > a.next"
override fun latestUpdatesNextPageSelector() = "div.next-page > a.next"
override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = "$baseUrl/search.php?name=$query&page=1&sort=views&order=za&${filters.map { it.id + "=1" }.joinToString("&")}&advopts=1" override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = "$baseUrl/search.php?name=$query&page=1&sort=views&order=za&${filters.map { it.id + "=1" }.joinToString("&")}&advopts=1"
override fun searchMangaSelector() = "div.result_search > dl:has(dt)" override fun searchMangaSelector() = "div.result_search > dl:has(dt)"
@ -146,4 +157,5 @@ class Mangahere(context: Context, override val id: Int) : ParsedOnlineSource(con
Filter("genres[Yaoi]", "Yaoi"), Filter("genres[Yaoi]", "Yaoi"),
Filter("genres[Yuri]", "Yuri") Filter("genres[Yuri]", "Yuri")
) )
} }

View File

@ -22,6 +22,8 @@ class Mangasee(context: Context, override val id: Int) : ParsedOnlineSource(cont
override val lang: Language get() = EN override val lang: Language get() = EN
override val supportsLatest = false
private val datePattern = Pattern.compile("(\\d+)\\s+(.*?)s? (from now|ago).*") private val datePattern = Pattern.compile("(\\d+)\\s+(.*?)s? (from now|ago).*")
private val dateFields = HashMap<String, Int>().apply { private val dateFields = HashMap<String, Int>().apply {
@ -168,4 +170,21 @@ class Mangasee(context: Context, override val id: Int) : ParsedOnlineSource(cont
Filter("Yaoi", "Yaoi"), Filter("Yaoi", "Yaoi"),
Filter("Yuri", "Yuri") Filter("Yuri", "Yuri")
) )
override fun latestUpdatesInitialUrl(): String {
throw UnsupportedOperationException("not implemented")
}
override fun latestUpdatesNextPageSelector(): String {
throw UnsupportedOperationException("not implemented")
}
override fun latestUpdatesFromElement(element: Element, manga: Manga) {
throw UnsupportedOperationException("not implemented")
}
override fun latestUpdatesSelector(): String {
throw UnsupportedOperationException("not implemented")
}
} }

View File

@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.network.POST import eu.kanade.tachiyomi.data.network.POST
import eu.kanade.tachiyomi.data.source.EN import eu.kanade.tachiyomi.data.source.EN
import eu.kanade.tachiyomi.data.source.Language import eu.kanade.tachiyomi.data.source.Language
import eu.kanade.tachiyomi.data.source.Source
import eu.kanade.tachiyomi.data.source.model.MangasPage import eu.kanade.tachiyomi.data.source.model.MangasPage
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.data.source.model.Page
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.data.source.online.OnlineSource
@ -26,6 +25,8 @@ class Readmangatoday(context: Context, override val id: Int) : ParsedOnlineSourc
override val lang: Language get() = EN override val lang: Language get() = EN
override val supportsLatest = false
override val client: OkHttpClient get() = network.cloudflareClient override val client: OkHttpClient get() = network.cloudflareClient
/** /**
@ -182,4 +183,21 @@ class Readmangatoday(context: Context, override val id: Int) : ParsedOnlineSourc
Filter("36", "Yaoi"), Filter("36", "Yaoi"),
Filter("37", "Yuri") Filter("37", "Yuri")
) )
override fun latestUpdatesInitialUrl(): String {
throw UnsupportedOperationException("not implemented")
}
override fun latestUpdatesNextPageSelector(): String {
throw UnsupportedOperationException("not implemented")
}
override fun latestUpdatesFromElement(element: Element, manga: Manga) {
throw UnsupportedOperationException("not implemented")
}
override fun latestUpdatesSelector(): String {
throw UnsupportedOperationException("not implemented")
}
} }

View File

@ -15,83 +15,95 @@ import java.text.SimpleDateFormat
class WieManga(context: Context, override val id: Int) : ParsedOnlineSource(context) { class WieManga(context: Context, override val id: Int) : ParsedOnlineSource(context) {
override val name = "Wie Manga!" override val name = "Wie Manga!"
override val baseUrl = "http://www.wiemanga.com" override val baseUrl = "http://www.wiemanga.com"
override val lang: Language get() = DE override val lang: Language get() = DE
override fun popularMangaInitialUrl() = "$baseUrl/list/Hot-Book/" override val supportsLatest = true
override fun popularMangaSelector() = ".booklist td > div" override fun popularMangaInitialUrl() = "$baseUrl/list/Hot-Book/"
override fun popularMangaFromElement(element: Element, manga: Manga) { override fun latestUpdatesInitialUrl() = "$baseUrl/list/New-Update/"
val image = element.select("dt img")
val title = element.select("dd a:first-child")
manga.setUrlWithoutDomain(title.attr("href")) override fun popularMangaSelector() = ".booklist td > div"
manga.title = title.text()
manga.thumbnail_url = image.attr("src") override fun latestUpdatesSelector() = ".booklist td > div"
override fun popularMangaFromElement(element: Element, manga: Manga) {
val image = element.select("dt img")
val title = element.select("dd a:first-child")
manga.setUrlWithoutDomain(title.attr("href"))
manga.title = title.text()
manga.thumbnail_url = image.attr("src")
}
override fun latestUpdatesFromElement(element: Element, manga: Manga) {
popularMangaFromElement(element, manga)
}
override fun popularMangaNextPageSelector() = null
override fun latestUpdatesNextPageSelector() = null
override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = "$baseUrl/search/?wd=$query"
override fun searchMangaSelector() = ".searchresult td > div"
override fun searchMangaFromElement(element: Element, manga: Manga) {
val image = element.select(".resultimg img")
val title = element.select(".resultbookname")
manga.setUrlWithoutDomain(title.attr("href"))
manga.title = title.text()
manga.thumbnail_url = image.attr("src")
}
override fun searchMangaNextPageSelector() = ".pagetor a.l"
override fun mangaDetailsParse(document: Document, manga: Manga) {
val imageElement = document.select(".bookmessgae tr > td:nth-child(1)").first()
val infoElement = document.select(".bookmessgae tr > td:nth-child(2)").first()
manga.author = infoElement.select("dd:nth-of-type(2) a").first()?.text()
manga.artist = infoElement.select("dd:nth-of-type(3) a").first()?.text()
manga.description = infoElement.select("dl > dt:last-child").first()?.text()?.replaceFirst("Beschreibung", "")
manga.thumbnail_url = imageElement.select("img").first()?.attr("src")
if (manga.author == "RSS")
manga.author = null
if (manga.artist == "RSS")
manga.artist = null
}
override fun chapterListSelector() = ".chapterlist tr:not(:first-child)"
override fun chapterFromElement(element: Element, chapter: Chapter) {
val urlElement = element.select(".col1 a").first()
val dateElement = element.select(".col3 a").first()
chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text()
chapter.date_upload = dateElement?.text()?.let { parseChapterDate(it) } ?: 0
}
private fun parseChapterDate(date: String): Long {
return SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(date).time
}
override fun pageListParse(response: Response, pages: MutableList<Page>) {
val document = response.asJsoup()
document.select("select#page").first().select("option").forEach {
pages.add(Page(pages.size, it.attr("value")))
} }
}
override fun popularMangaNextPageSelector() = null override fun pageListParse(document: Document, pages: MutableList<Page>) {}
override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = "$baseUrl/search/?wd=$query" override fun imageUrlParse(document: Document) = document.select("img#comicpic").first().attr("src")
override fun searchMangaSelector() = ".searchresult td > div"
override fun searchMangaFromElement(element: Element, manga: Manga) {
val image = element.select(".resultimg img")
val title = element.select(".resultbookname")
manga.setUrlWithoutDomain(title.attr("href"))
manga.title = title.text()
manga.thumbnail_url = image.attr("src")
}
override fun searchMangaNextPageSelector() = ".pagetor a.l"
override fun mangaDetailsParse(document: Document, manga: Manga) {
val imageElement = document.select(".bookmessgae tr > td:nth-child(1)").first()
val infoElement = document.select(".bookmessgae tr > td:nth-child(2)").first()
manga.author = infoElement.select("dd:nth-of-type(2) a").first()?.text()
manga.artist = infoElement.select("dd:nth-of-type(3) a").first()?.text()
manga.description = infoElement.select("dl > dt:last-child").first()?.text()?.replaceFirst("Beschreibung", "")
manga.thumbnail_url = imageElement.select("img").first()?.attr("src")
if (manga.author == "RSS")
manga.author = null
if (manga.artist == "RSS")
manga.artist = null
}
override fun chapterListSelector() = ".chapterlist tr:not(:first-child)"
override fun chapterFromElement(element: Element, chapter: Chapter) {
val urlElement = element.select(".col1 a").first()
val dateElement = element.select(".col3 a").first()
chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text()
chapter.date_upload = dateElement?.text()?.let { parseChapterDate(it) } ?: 0
}
private fun parseChapterDate(date: String): Long {
return SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(date).time
}
override fun pageListParse(response: Response, pages: MutableList<Page>) {
val document = response.asJsoup()
document.select("select#page").first().select("option").forEach {
pages.add(Page(pages.size, it.attr("value")))
}
}
override fun pageListParse(document: Document, pages: MutableList<Page>) {}
override fun imageUrlParse(document: Document) = document.select("img#comicpic").first().attr("src")
} }

View File

@ -21,12 +21,18 @@ class Mangachan(context: Context, override val id: Int) : ParsedOnlineSource(con
override val lang: Language get() = RU override val lang: Language get() = RU
override val supportsLatest = true
override fun popularMangaInitialUrl() = "$baseUrl/mostfavorites" override fun popularMangaInitialUrl() = "$baseUrl/mostfavorites"
override fun latestUpdatesInitialUrl() = "$baseUrl/manga/new"
override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = "$baseUrl/?do=search&subaction=search&story=$query" override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = "$baseUrl/?do=search&subaction=search&story=$query"
override fun popularMangaSelector() = "div.content_row" override fun popularMangaSelector() = "div.content_row"
override fun latestUpdatesSelector() = "div.content_row"
override fun popularMangaFromElement(element: Element, manga: Manga) { override fun popularMangaFromElement(element: Element, manga: Manga) {
element.select("h2 > a").first().let { element.select("h2 > a").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
@ -34,8 +40,14 @@ class Mangachan(context: Context, override val id: Int) : ParsedOnlineSource(con
} }
} }
override fun latestUpdatesFromElement(element: Element, manga: Manga) {
popularMangaFromElement(element, manga)
}
override fun popularMangaNextPageSelector() = "a:contains(Вперед)" override fun popularMangaNextPageSelector() = "a:contains(Вперед)"
override fun latestUpdatesNextPageSelector() = "a:contains(Вперед)"
override fun searchMangaSelector() = popularMangaSelector() override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaFromElement(element: Element, manga: Manga) { override fun searchMangaFromElement(element: Element, manga: Manga) {
@ -91,4 +103,5 @@ class Mangachan(context: Context, override val id: Int) : ParsedOnlineSource(con
override fun pageListParse(document: Document, pages: MutableList<Page>) { } override fun pageListParse(document: Document, pages: MutableList<Page>) { }
override fun imageUrlParse(document: Document) = "" override fun imageUrlParse(document: Document) = ""
} }

View File

@ -22,13 +22,19 @@ class Mintmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
override val lang: Language get() = RU override val lang: Language get() = RU
override val supportsLatest = true
override fun popularMangaInitialUrl() = "$baseUrl/list?sortType=rate" override fun popularMangaInitialUrl() = "$baseUrl/list?sortType=rate"
override fun latestUpdatesInitialUrl() = "$baseUrl/list?sortType=updated"
override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = override fun searchMangaInitialUrl(query: String, filters: List<Filter>) =
"$baseUrl/search?q=$query&${filters.map { it.id + "=in" }.joinToString("&")}" "$baseUrl/search?q=$query&${filters.map { it.id + "=in" }.joinToString("&")}"
override fun popularMangaSelector() = "div.desc" override fun popularMangaSelector() = "div.desc"
override fun latestUpdatesSelector() = "div.desc"
override fun popularMangaFromElement(element: Element, manga: Manga) { override fun popularMangaFromElement(element: Element, manga: Manga) {
element.select("h3 > a").first().let { element.select("h3 > a").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
@ -36,8 +42,14 @@ class Mintmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
} }
} }
override fun latestUpdatesFromElement(element: Element, manga: Manga) {
popularMangaFromElement(element, manga)
}
override fun popularMangaNextPageSelector() = "a.nextLink" override fun popularMangaNextPageSelector() = "a.nextLink"
override fun latestUpdatesNextPageSelector() = "a.nextLink"
override fun searchMangaSelector() = popularMangaSelector() override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaFromElement(element: Element, manga: Manga) { override fun searchMangaFromElement(element: Element, manga: Manga) {
@ -151,4 +163,5 @@ class Mintmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
Filter("el_1315", "юри"), Filter("el_1315", "юри"),
Filter("el_1336", "яой") Filter("el_1336", "яой")
) )
} }

View File

@ -22,13 +22,19 @@ class Readmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
override val lang: Language get() = RU override val lang: Language get() = RU
override val supportsLatest = true
override fun popularMangaInitialUrl() = "$baseUrl/list?sortType=rate" override fun popularMangaInitialUrl() = "$baseUrl/list?sortType=rate"
override fun latestUpdatesInitialUrl() = "$baseUrl/list?sortType=updated"
override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = override fun searchMangaInitialUrl(query: String, filters: List<Filter>) =
"$baseUrl/search?q=$query&${filters.map { it.id + "=in" }.joinToString("&")}" "$baseUrl/search?q=$query&${filters.map { it.id + "=in" }.joinToString("&")}"
override fun popularMangaSelector() = "div.desc" override fun popularMangaSelector() = "div.desc"
override fun latestUpdatesSelector() = "div.desc"
override fun popularMangaFromElement(element: Element, manga: Manga) { override fun popularMangaFromElement(element: Element, manga: Manga) {
element.select("h3 > a").first().let { element.select("h3 > a").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
@ -36,8 +42,14 @@ class Readmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
} }
} }
override fun latestUpdatesFromElement(element: Element, manga: Manga) {
popularMangaFromElement(element, manga)
}
override fun popularMangaNextPageSelector() = "a.nextLink" override fun popularMangaNextPageSelector() = "a.nextLink"
override fun latestUpdatesNextPageSelector() = "a.nextLink"
override fun searchMangaSelector() = popularMangaSelector() override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaFromElement(element: Element, manga: Manga) { override fun searchMangaFromElement(element: Element, manga: Manga) {
@ -152,4 +164,5 @@ class Readmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
Filter("el_2149", "этти"), Filter("el_2149", "этти"),
Filter("el_2123", "юри") Filter("el_2123", "юри")
) )
} }

View File

@ -41,7 +41,7 @@ import java.util.concurrent.TimeUnit.MILLISECONDS
* Uses R.layout.fragment_catalogue. * Uses R.layout.fragment_catalogue.
*/ */
@RequiresPresenter(CataloguePresenter::class) @RequiresPresenter(CataloguePresenter::class)
class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHolder.OnListItemClickListener { open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHolder.OnListItemClickListener {
/** /**
* Spinner shown in the toolbar to change the selected source. * Spinner shown in the toolbar to change the selected source.

View File

@ -4,19 +4,10 @@ import eu.kanade.tachiyomi.data.source.model.MangasPage
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.data.source.online.OnlineSource
import eu.kanade.tachiyomi.data.source.online.OnlineSource.Filter import eu.kanade.tachiyomi.data.source.online.OnlineSource.Filter
import rx.Observable import rx.Observable
import rx.subjects.PublishSubject
class CataloguePager(val source: OnlineSource, val query: String, val filters: List<Filter>) { open class CataloguePager(val source: OnlineSource, val query: String, val filters: List<Filter>): Pager() {
private var lastPage: MangasPage? = null override fun requestNext(transformer: (Observable<MangasPage>) -> Observable<MangasPage>): Observable<MangasPage> {
private val results = PublishSubject.create<MangasPage>()
fun results(): Observable<MangasPage> {
return results.asObservable()
}
fun requestNext(transformer: (Observable<MangasPage>) -> Observable<MangasPage>): Observable<MangasPage> {
val lastPage = lastPage val lastPage = lastPage
val page = if (lastPage == null) val page = if (lastPage == null)
@ -34,8 +25,4 @@ class CataloguePager(val source: OnlineSource, val query: String, val filters: L
.doOnNext { this@CataloguePager.lastPage = it } .doOnNext { this@CataloguePager.lastPage = it }
} }
fun hasNextPage(): Boolean {
return lastPage == null || lastPage?.nextPageUrl != null
}
} }

View File

@ -26,7 +26,7 @@ import java.util.NoSuchElementException
/** /**
* Presenter of [CatalogueFragment]. * Presenter of [CatalogueFragment].
*/ */
class CataloguePresenter : BasePresenter<CatalogueFragment>() { open class CataloguePresenter : BasePresenter<CatalogueFragment>() {
/** /**
* Source manager. * Source manager.
@ -73,7 +73,7 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
/** /**
* Pager containing a list of manga results. * Pager containing a list of manga results.
*/ */
private lateinit var pager: CataloguePager private lateinit var pager: Pager
/** /**
* Subject that initializes a list of manga. * Subject that initializes a list of manga.
@ -140,7 +140,7 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
} }
// Create a new pager. // Create a new pager.
pager = CataloguePager(source, query, filters) pager = createPager(query, filters)
// Prepare the pager. // Prepare the pager.
pagerSubscription?.let { remove(it) } pagerSubscription?.let { remove(it) }
@ -305,7 +305,7 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
* @param source the source to check. * @param source the source to check.
* @return true if the source is valid, false otherwise. * @return true if the source is valid, false otherwise.
*/ */
fun isValidSource(source: Source?): Boolean { open fun isValidSource(source: Source?): Boolean {
if (source == null) return false if (source == null) return false
if (source is LoginSource) { if (source is LoginSource) {
@ -327,7 +327,7 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
/** /**
* Returns a list of enabled sources ordered by language and name. * Returns a list of enabled sources ordered by language and name.
*/ */
private fun getEnabledSources(): List<OnlineSource> { open protected fun getEnabledSources(): List<OnlineSource> {
val languages = prefs.enabledLanguages().getOrDefault() val languages = prefs.enabledLanguages().getOrDefault()
val hiddenCatalogues = prefs.hiddenCatalogues().getOrDefault() val hiddenCatalogues = prefs.hiddenCatalogues().getOrDefault()
@ -371,4 +371,8 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
restartPager(filters = selectedFilters) restartPager(filters = selectedFilters)
} }
open fun createPager(query: String, filters: List<Filter>): Pager {
return CataloguePager(source, query, filters)
}
} }

View File

@ -0,0 +1,25 @@
package eu.kanade.tachiyomi.ui.catalogue
import eu.kanade.tachiyomi.data.source.model.MangasPage
import rx.subjects.PublishSubject
import rx.Observable
/**
* A general pager for source requests (latest updates, popular, search)
*/
abstract class Pager {
protected var lastPage: MangasPage? = null
protected val results = PublishSubject.create<MangasPage>()
fun results(): Observable<MangasPage> {
return results.asObservable()
}
fun hasNextPage(): Boolean {
return lastPage == null || lastPage?.nextPageUrl != null
}
abstract fun requestNext(transformer: (Observable<MangasPage>) -> Observable<MangasPage>): Observable<MangasPage>
}

View File

@ -0,0 +1,29 @@
package eu.kanade.tachiyomi.ui.latest_updates
import eu.kanade.tachiyomi.ui.catalogue.CatalogueFragment
import nucleus.factory.RequiresPresenter
import android.view.*
import eu.kanade.tachiyomi.R
/**
* Fragment that shows the manga from the catalogue. Inherit CatalogueFragment.
*/
@RequiresPresenter(LatestUpdatesPresenter::class)
class LatestUpdatesFragment : CatalogueFragment() {
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
menu.findItem(R.id.action_search).isVisible = false
menu.findItem(R.id.action_set_filter).isVisible = false
}
companion object {
fun newInstance(): LatestUpdatesFragment {
return LatestUpdatesFragment()
}
}
}

View File

@ -0,0 +1,28 @@
package eu.kanade.tachiyomi.ui.latest_updates
import eu.kanade.tachiyomi.data.source.model.MangasPage
import eu.kanade.tachiyomi.data.source.online.OnlineSource
import eu.kanade.tachiyomi.ui.catalogue.Pager
import rx.Observable
/**
* LatestUpdatesPager inherited from the general Pager.
*/
class LatestUpdatesPager(val source: OnlineSource): Pager() {
override fun requestNext(transformer: (Observable<MangasPage>) -> Observable<MangasPage>): Observable<MangasPage> {
val lastPage = lastPage
val page = if (lastPage == null)
MangasPage(1)
else
MangasPage(lastPage.page + 1).apply { url = lastPage.nextPageUrl!! }
val observable = source.fetchLatestUpdates(page)
return transformer(observable)
.doOnNext { results.onNext(it) }
.doOnNext { this@LatestUpdatesPager.lastPage = it }
}
}

View File

@ -0,0 +1,26 @@
package eu.kanade.tachiyomi.ui.latest_updates
import eu.kanade.tachiyomi.data.source.Source
import eu.kanade.tachiyomi.data.source.online.OnlineSource
import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter
import eu.kanade.tachiyomi.ui.catalogue.Pager
import eu.kanade.tachiyomi.data.source.online.OnlineSource.Filter
/**
* Presenter of [LatestUpdatesFragment]. Inherit CataloguePresenter.
*/
class LatestUpdatesPresenter : CataloguePresenter() {
override fun createPager(query: String, filters: List<Filter>): Pager {
return LatestUpdatesPager(source)
}
override fun getEnabledSources(): List<OnlineSource> {
return super.getEnabledSources().filter { it.supportsLatest }
}
override fun isValidSource(source: Source?): Boolean {
return super.isValidSource(source) && (source as OnlineSource).supportsLatest
}
}

View File

@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.backup.BackupFragment import eu.kanade.tachiyomi.ui.backup.BackupFragment
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import eu.kanade.tachiyomi.ui.catalogue.CatalogueFragment import eu.kanade.tachiyomi.ui.catalogue.CatalogueFragment
import eu.kanade.tachiyomi.ui.latest_updates.LatestUpdatesFragment
import eu.kanade.tachiyomi.ui.download.DownloadFragment import eu.kanade.tachiyomi.ui.download.DownloadFragment
import eu.kanade.tachiyomi.ui.library.LibraryFragment import eu.kanade.tachiyomi.ui.library.LibraryFragment
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersFragment import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersFragment
@ -58,9 +59,10 @@ class MainActivity : BaseActivity() {
val id = item.itemId val id = item.itemId
when (id) { when (id) {
R.id.nav_drawer_library -> setFragment(LibraryFragment.newInstance(), id) R.id.nav_drawer_library -> setFragment(LibraryFragment.newInstance(), id)
R.id.nav_drawer_recently_read -> setFragment(RecentlyReadFragment.newInstance(), id)
R.id.nav_drawer_recent_updates -> setFragment(RecentChaptersFragment.newInstance(), id) R.id.nav_drawer_recent_updates -> setFragment(RecentChaptersFragment.newInstance(), id)
R.id.nav_drawer_recently_read -> setFragment(RecentlyReadFragment.newInstance(), id)
R.id.nav_drawer_catalogues -> setFragment(CatalogueFragment.newInstance(), id) R.id.nav_drawer_catalogues -> setFragment(CatalogueFragment.newInstance(), id)
R.id.nav_drawer_latest_updates -> setFragment(LatestUpdatesFragment.newInstance(), id)
R.id.nav_drawer_downloads -> setFragment(DownloadFragment.newInstance(), id) R.id.nav_drawer_downloads -> setFragment(DownloadFragment.newInstance(), id)
R.id.nav_drawer_settings -> { R.id.nav_drawer_settings -> {
val intent = Intent(this, SettingsActivity::class.java) val intent = Intent(this, SettingsActivity::class.java)

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10 10,-4.5 10,-10S17.5,2 12,2zM16.2,16.2L11,13L11,7h1.5v5.2l4.5,2.7 -0.8,1.3z"/>
</vector>

View File

@ -19,7 +19,8 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?android:attr/colorBackground" android:background="?android:attr/colorBackground"
tools:background="?android:attr/colorBackground" tools:background="?android:attr/colorBackground"
tools:src="@mipmap/ic_launcher" /> tools:src="@mipmap/ic_launcher"
tools:ignore="ContentDescription" />
<View <View
android:id="@+id/gradient" android:id="@+id/gradient"

View File

@ -7,19 +7,22 @@
android:id="@+id/nav_drawer_library" android:id="@+id/nav_drawer_library"
android:icon="@drawable/ic_book_black_24dp" android:icon="@drawable/ic_book_black_24dp"
android:title="@string/label_library" /> android:title="@string/label_library" />
<item
android:id="@+id/nav_drawer_recent_updates"
android:icon="@drawable/ic_update_black_24dp"
android:title="@string/label_recent_updates" />
<item <item
android:id="@+id/nav_drawer_recently_read" android:id="@+id/nav_drawer_recently_read"
android:icon="@drawable/ic_glasses_black_24dp" android:icon="@drawable/ic_glasses_black_24dp"
android:title="@string/label_recent_manga"/> android:title="@string/label_recent_manga"/>
<item
android:id="@+id/nav_drawer_recent_updates"
android:icon="@drawable/ic_update_black_24dp"
android:title="@string/label_recent_updates" />
<item <item
android:id="@+id/nav_drawer_catalogues" android:id="@+id/nav_drawer_catalogues"
android:icon="@drawable/ic_explore_black_24dp" android:icon="@drawable/ic_explore_black_24dp"
android:title="@string/label_catalogues" /> android:title="@string/label_catalogues" />
<item
android:id="@+id/nav_drawer_latest_updates"
android:icon="@drawable/ic_watch_later"
android:title="@string/label_latest_updates" />
<item <item
android:id="@+id/nav_drawer_downloads" android:id="@+id/nav_drawer_downloads"
android:icon="@drawable/ic_file_download_black_24dp" android:icon="@drawable/ic_file_download_black_24dp"

View File

@ -8,8 +8,9 @@
<string name="label_download_queue">Download queue</string> <string name="label_download_queue">Download queue</string>
<string name="label_library">My library</string> <string name="label_library">My library</string>
<string name="label_recent_manga">Recently read</string> <string name="label_recent_manga">Recently read</string>
<string name="label_recent_updates">Recent updates</string>
<string name="label_catalogues">Catalogues</string> <string name="label_catalogues">Catalogues</string>
<string name="label_recent_updates">Library updates</string>
<string name="label_latest_updates">Latest updates</string>
<string name="label_categories">Categories</string> <string name="label_categories">Categories</string>
<string name="label_selected">Selected: %1$d</string> <string name="label_selected">Selected: %1$d</string>
<string name="label_backup">Backup</string> <string name="label_backup">Backup</string>

View File

@ -18,4 +18,4 @@ allprojects {
jcenter() jcenter()
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
} }
} }