mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 22:37:56 +01:00 
			
		
		
		
	Improve catalog search filters (#615)
* Add three state (include/exclude/ignore) search filters (works for now only on MangaFox and MangaHere) * checkbox icons in xml format * fix checkbox icons referencing * fix three states filters in remaining catalogs * use Spinner for filter with more than three states (Mangasee) * use EditText for freetext filters (Mangasee) * remove pngs * Filter class/subclass * add Filter.Header * English catalogs
This commit is contained in:
		| @@ -53,7 +53,7 @@ abstract class OnlineSource() : Source { | ||||
|     /** | ||||
|      * Whether the source has support for latest updates. | ||||
|      */ | ||||
|     abstract val supportsLatest : Boolean | ||||
|     abstract val supportsLatest: Boolean | ||||
|  | ||||
|     /** | ||||
|      * Headers used for requests. | ||||
| @@ -133,7 +133,7 @@ abstract class OnlineSource() : Source { | ||||
|      *             the current page and the next page url. | ||||
|      * @param query the search query. | ||||
|      */ | ||||
|     open fun fetchSearchManga(page: MangasPage, query: String, filters: List<Filter>): Observable<MangasPage> = client | ||||
|     open fun fetchSearchManga(page: MangasPage, query: String, filters: List<Filter<*>>): Observable<MangasPage> = client | ||||
|             .newCall(searchMangaRequest(page, query, filters)) | ||||
|             .asObservableSuccess() | ||||
|             .map { response -> | ||||
| @@ -148,7 +148,7 @@ abstract class OnlineSource() : Source { | ||||
|      * @param page the page object. | ||||
|      * @param query the search query. | ||||
|      */ | ||||
|     open protected fun searchMangaRequest(page: MangasPage, query: String, filters: List<Filter>): Request { | ||||
|     open protected fun searchMangaRequest(page: MangasPage, query: String, filters: List<Filter<*>>): Request { | ||||
|         if (page.page == 1) { | ||||
|             page.url = searchMangaInitialUrl(query, filters) | ||||
|         } | ||||
| @@ -160,7 +160,7 @@ abstract class OnlineSource() : Source { | ||||
|      * | ||||
|      * @param query the search query. | ||||
|      */ | ||||
|     abstract protected fun searchMangaInitialUrl(query: String, filters: List<Filter>): String | ||||
|     abstract protected fun searchMangaInitialUrl(query: String, filters: List<Filter<*>>): String | ||||
|  | ||||
|     /** | ||||
|      * Parse the response from the site. It should add a list of manga and the absolute url to the | ||||
| @@ -170,7 +170,7 @@ abstract class OnlineSource() : Source { | ||||
|      * @param page the page object to be filled. | ||||
|      * @param query the search query. | ||||
|      */ | ||||
|     abstract protected fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List<Filter>) | ||||
|     abstract protected fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List<Filter<*>>) | ||||
|  | ||||
|     /** | ||||
|      * Returns an observable containing a page with a list of latest manga. | ||||
| @@ -365,10 +365,10 @@ abstract class OnlineSource() : Source { | ||||
|      * @param page the page whose source image has to be downloaded. | ||||
|      */ | ||||
|     final override fun fetchImage(page: Page): Observable<Page> = | ||||
|         if (page.imageUrl.isNullOrEmpty()) | ||||
|             fetchImageUrl(page).flatMap { getCachedImage(it) } | ||||
|         else | ||||
|             getCachedImage(page) | ||||
|             if (page.imageUrl.isNullOrEmpty()) | ||||
|                 fetchImageUrl(page).flatMap { getCachedImage(it) } | ||||
|             else | ||||
|                 getCachedImage(page) | ||||
|  | ||||
|     /** | ||||
|      * Returns an observable with the response of the source image. | ||||
| @@ -460,10 +460,21 @@ abstract class OnlineSource() : Source { | ||||
|      * @param manga the manga of the chapter. | ||||
|      */ | ||||
|     open fun prepareNewChapter(chapter: Chapter, manga: Manga) { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     data class Filter(val id: String, val name: String) | ||||
|     sealed class Filter<T>(val name: String, var state: T) { | ||||
|         open class Header(name: String) : Filter<Any>(name, 0) | ||||
|         abstract class List<V>(name: String, val values: Array<V>, state: Int = 0) : Filter<Int>(name, state) | ||||
|         abstract class Text(name: String, state: String = "") : Filter<String>(name, state) | ||||
|         abstract class CheckBox(name: String, state: Boolean = false) : Filter<Boolean>(name, state) | ||||
|         abstract class TriState(name: String, state: Int = STATE_IGNORE) : Filter<Int>(name, state) { | ||||
|             companion object { | ||||
|                 const val STATE_IGNORE = 0 | ||||
|                 const val STATE_INCLUDE = 1 | ||||
|                 const val STATE_EXCLUDE = 2 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     open fun getFilterList(): List<Filter> = emptyList() | ||||
|     open fun getFilterList(): List<Filter<*>> = emptyList() | ||||
| } | ||||
|   | ||||
| @@ -61,7 +61,7 @@ abstract class ParsedOnlineSource() : OnlineSource() { | ||||
|      * @param page the page object to be filled. | ||||
|      * @param query the search query. | ||||
|      */ | ||||
|     override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List<Filter>) { | ||||
|     override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List<Filter<*>>) { | ||||
|         val document = response.asJsoup() | ||||
|         for (element in document.select(searchMangaSelector())) { | ||||
|             Manga.create(id).apply { | ||||
|   | ||||
| @@ -30,7 +30,7 @@ class YamlOnlineSource(mappings: Map<*, *>) : OnlineSource() { | ||||
|  | ||||
|     override val supportsLatest = map.latestupdates != null | ||||
|  | ||||
|     override val client = when(map.client) { | ||||
|     override val client = when (map.client) { | ||||
|         "cloudflare" -> network.cloudflareClient | ||||
|         else -> network.client | ||||
|     } | ||||
| @@ -66,7 +66,7 @@ class YamlOnlineSource(mappings: Map<*, *>) : OnlineSource() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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) { | ||||
|             page.url = searchMangaInitialUrl(query, filters) | ||||
|         } | ||||
| @@ -76,9 +76,9 @@ class YamlOnlineSource(mappings: Map<*, *>) : OnlineSource() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = map.search.url.replace("\$query", query) | ||||
|     override fun searchMangaInitialUrl(query: String, filters: List<Filter<*>>) = map.search.url.replace("\$query", query) | ||||
|  | ||||
|     override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List<Filter>) { | ||||
|     override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List<Filter<*>>) { | ||||
|         val document = response.asJsoup() | ||||
|         for (element in document.select(map.search.manga_css)) { | ||||
|             Manga.create(id).apply { | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| package eu.kanade.tachiyomi.data.source.online.english | ||||
|  | ||||
| import android.net.Uri | ||||
| import android.text.Html | ||||
| import eu.kanade.tachiyomi.data.database.models.Chapter | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| @@ -14,6 +13,7 @@ import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource | ||||
| import eu.kanade.tachiyomi.util.asJsoup | ||||
| import eu.kanade.tachiyomi.util.selectText | ||||
| import okhttp3.FormBody | ||||
| import okhttp3.HttpUrl | ||||
| import okhttp3.Request | ||||
| import okhttp3.Response | ||||
| import org.jsoup.nodes.Document | ||||
| @@ -107,26 +107,46 @@ class Batoto(override val id: Int) : ParsedOnlineSource(), LoginSource { | ||||
|  | ||||
|     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${getFilterParams(filters)}" | ||||
|     override fun searchMangaInitialUrl(query: String, filters: List<Filter<*>>) = searchMangaUrl(query, filters, 1) | ||||
|  | ||||
|     private fun getFilterParams(filters: List<Filter>): String { | ||||
|     private fun searchMangaUrl(query: String, filterStates: List<Filter<*>>, page: Int): String { | ||||
|         val url = HttpUrl.parse("$baseUrl/search_ajax").newBuilder() | ||||
|         if (!query.isEmpty()) url.addQueryParameter("name", query).addQueryParameter("name_cond", "c") | ||||
|         var genres = "" | ||||
|         var completed = "" | ||||
|         for (filter in filters) { | ||||
|             if (filter.equals(completedFilter)) completed = "&completed=c" | ||||
|             else genres += ";i" + filter.id | ||||
|         for (filter in if (filterStates.isEmpty()) filters else filterStates) { | ||||
|             when (filter) { | ||||
|                 is Status -> if (filter.state != Filter.TriState.STATE_IGNORE) { | ||||
|                     url.addQueryParameter("completed", if (filter.state == Filter.TriState.STATE_EXCLUDE) "i" else "c") | ||||
|                 } | ||||
|                 is Genre -> if (filter.state != Filter.TriState.STATE_IGNORE) { | ||||
|                     genres += (if (filter.state == Filter.TriState.STATE_EXCLUDE) ";e" else ";i") + filter.id | ||||
|                 } | ||||
|                 is TextField -> { | ||||
|                     if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state) | ||||
|                 } | ||||
|                 is ListField -> { | ||||
|                     val sel = filter.values[filter.state].value | ||||
|                     if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel) | ||||
|                 } | ||||
|                 is Flag -> { | ||||
|                     val sel = if (filter.state) filter.valTrue else filter.valFalse | ||||
|                     if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return if (genres.isEmpty()) completed else "&genres=$genres&genre_cond=and$completed" | ||||
|         if (!genres.isEmpty()) url.addQueryParameter("genres", genres) | ||||
|         url.addQueryParameter("p", page.toString()) | ||||
|         return url.toString() | ||||
|     } | ||||
|  | ||||
|     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) { | ||||
|             page.url = searchMangaInitialUrl(query, filters) | ||||
|         } | ||||
|         return GET(page.url, headers) | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List<Filter>) { | ||||
|     override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List<Filter<*>>) { | ||||
|         val document = response.asJsoup() | ||||
|         for (element in document.select(searchMangaSelector())) { | ||||
|             Manga.create(id).apply { | ||||
| @@ -136,7 +156,7 @@ class Batoto(override val id: Int) : ParsedOnlineSource(), LoginSource { | ||||
|         } | ||||
|  | ||||
|         page.nextPageUrl = document.select(searchMangaNextPageSelector()).first()?.let { | ||||
|             "$baseUrl/search_ajax?name=${Uri.encode(query)}&order_cond=views&order=desc&p=${page.page + 1}${getFilterParams(filters)}" | ||||
|             searchMangaUrl(query, filters, page.page + 1) | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -304,51 +324,69 @@ class Batoto(override val id: Int) : ParsedOnlineSource(), LoginSource { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private val completedFilter = Filter("completed", "Completed") | ||||
|     private data class ListValue(val name: String, val value: String) { | ||||
|         override fun toString(): String = name | ||||
|     } | ||||
|  | ||||
|     private class Status() : Filter.TriState("Completed") | ||||
|     private class Genre(name: String, val id: Int) : Filter.TriState(name) | ||||
|     private class TextField(name: String, val key: String) : Filter.Text(name) | ||||
|     private class ListField(name: String, val key: String, values: Array<ListValue>, state: Int = 0) : Filter.List<ListValue>(name, values, state) | ||||
|     private class Flag(name: String, val key: String, val valTrue: String, val valFalse: String) : Filter.CheckBox(name) | ||||
|  | ||||
|     // [...document.querySelectorAll("#advanced_options div.genre_buttons")].map((el,i) => { | ||||
|     //     const onClick=el.getAttribute('onclick');const id=onClick.substr(14,onClick.length-16);return `Filter("${id}", "${el.textContent.trim()}")` | ||||
|     //     const onClick=el.getAttribute('onclick');const id=onClick.substr(14,onClick.length-16);return `Genre("${el.textContent.trim()}", ${id})` | ||||
|     // }).join(',\n') | ||||
|     // on https://bato.to/search | ||||
|     override fun getFilterList(): List<Filter> = listOf( | ||||
|             completedFilter, | ||||
|             Filter("40", "4-Koma"), | ||||
|             Filter("1", "Action"), | ||||
|             Filter("2", "Adventure"), | ||||
|             Filter("39", "Award Winning"), | ||||
|             Filter("3", "Comedy"), | ||||
|             Filter("41", "Cooking"), | ||||
|             Filter("9", "Doujinshi"), | ||||
|             Filter("10", "Drama"), | ||||
|             Filter("12", "Ecchi"), | ||||
|             Filter("13", "Fantasy"), | ||||
|             Filter("15", "Gender Bender"), | ||||
|             Filter("17", "Harem"), | ||||
|             Filter("20", "Historical"), | ||||
|             Filter("22", "Horror"), | ||||
|             Filter("34", "Josei"), | ||||
|             Filter("27", "Martial Arts"), | ||||
|             Filter("30", "Mecha"), | ||||
|             Filter("42", "Medical"), | ||||
|             Filter("37", "Music"), | ||||
|             Filter("4", "Mystery"), | ||||
|             Filter("38", "Oneshot"), | ||||
|             Filter("5", "Psychological"), | ||||
|             Filter("6", "Romance"), | ||||
|             Filter("7", "School Life"), | ||||
|             Filter("8", "Sci-fi"), | ||||
|             Filter("32", "Seinen"), | ||||
|             Filter("35", "Shoujo"), | ||||
|             Filter("16", "Shoujo Ai"), | ||||
|             Filter("33", "Shounen"), | ||||
|             Filter("19", "Shounen Ai"), | ||||
|             Filter("21", "Slice of Life"), | ||||
|             Filter("23", "Smut"), | ||||
|             Filter("25", "Sports"), | ||||
|             Filter("26", "Supernatural"), | ||||
|             Filter("28", "Tragedy"), | ||||
|             Filter("36", "Webtoon"), | ||||
|             Filter("29", "Yaoi"), | ||||
|             Filter("31", "Yuri") | ||||
|     override fun getFilterList(): List<Filter<*>> = listOf( | ||||
|             TextField("Author", "artist_name"), | ||||
|             ListField("Type", "type", arrayOf(ListValue("Any", ""), ListValue("Manga (Jp)", "jp"), ListValue("Manhwa (Kr)", "kr"), ListValue("Manhua (Cn)", "cn"), ListValue("Artbook", "ar"), ListValue("Other", "ot"))), | ||||
|             Status(), | ||||
|             Flag("Exclude mature", "mature", "m", ""), | ||||
|             Filter.Header(""), | ||||
|             ListField("Order by", "order_cond", arrayOf(ListValue("Title", "title"), ListValue("Author", "author"), ListValue("Artist", "artist"), ListValue("Rating", "rating"), ListValue("Views", "views"), ListValue("Last Update", "update")), 4), | ||||
|             Flag("Ascending order", "order", "asc", "desc"), | ||||
|             Filter.Header("Genres"), | ||||
|             ListField("Inclusion mode", "genre_cond", arrayOf(ListValue("And (all selected genres)", "and"), ListValue("Or (any selected genres) ", "or"))), | ||||
|             Genre("4-Koma", 40), | ||||
|             Genre("Action", 1), | ||||
|             Genre("Adventure", 2), | ||||
|             Genre("Award Winning", 39), | ||||
|             Genre("Comedy", 3), | ||||
|             Genre("Cooking", 41), | ||||
|             Genre("Doujinshi", 9), | ||||
|             Genre("Drama", 10), | ||||
|             Genre("Ecchi", 12), | ||||
|             Genre("Fantasy", 13), | ||||
|             Genre("Gender Bender", 15), | ||||
|             Genre("Harem", 17), | ||||
|             Genre("Historical", 20), | ||||
|             Genre("Horror", 22), | ||||
|             Genre("Josei", 34), | ||||
|             Genre("Martial Arts", 27), | ||||
|             Genre("Mecha", 30), | ||||
|             Genre("Medical", 42), | ||||
|             Genre("Music", 37), | ||||
|             Genre("Mystery", 4), | ||||
|             Genre("Oneshot", 38), | ||||
|             Genre("Psychological", 5), | ||||
|             Genre("Romance", 6), | ||||
|             Genre("School Life", 7), | ||||
|             Genre("Sci-fi", 8), | ||||
|             Genre("Seinen", 32), | ||||
|             Genre("Shoujo", 35), | ||||
|             Genre("Shoujo Ai", 16), | ||||
|             Genre("Shounen", 33), | ||||
|             Genre("Shounen Ai", 19), | ||||
|             Genre("Slice of Life", 21), | ||||
|             Genre("Smut", 23), | ||||
|             Genre("Sports", 25), | ||||
|             Genre("Supernatural", 26), | ||||
|             Genre("Tragedy", 28), | ||||
|             Genre("Webtoon", 36), | ||||
|             Genre("Yaoi", 29), | ||||
|             Genre("Yuri", 31), | ||||
|             Genre("[no chapters]", 44) | ||||
|     ) | ||||
|  | ||||
| } | ||||
| @@ -51,25 +51,26 @@ class Kissmanga(override val id: Int) : ParsedOnlineSource() { | ||||
|  | ||||
|     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) { | ||||
|             page.url = searchMangaInitialUrl(query, filters) | ||||
|         } | ||||
|  | ||||
|         val form = FormBody.Builder().apply { | ||||
|             add("authorArtist", "") | ||||
|             add("mangaName", query) | ||||
|  | ||||
|             this@Kissmanga.filters.forEach { filter -> | ||||
|                 if (filter.equals(completedFilter)) add("status", if (filter in filters) filter.id else "") | ||||
|                 else add("genres", if (filter in filters) "1" else "0") | ||||
|             for (filter in if (filters.isEmpty()) this@Kissmanga.filters else filters) { | ||||
|                 when (filter) { | ||||
|                     is Author -> add("authorArtist", filter.state) | ||||
|                     is Status -> add("status", arrayOf("", "Completed", "Ongoing")[filter.state]) | ||||
|                     is Genre -> add("genres", filter.state.toString()) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return POST(page.url, headers, form.build()) | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = "$baseUrl/AdvanceSearch" | ||||
|     override fun searchMangaInitialUrl(query: String, filters: List<Filter<*>>) = "$baseUrl/AdvanceSearch" | ||||
|  | ||||
|     override fun searchMangaSelector() = popularMangaSelector() | ||||
|  | ||||
| @@ -128,54 +129,59 @@ class Kissmanga(override val id: Int) : ParsedOnlineSource() { | ||||
|  | ||||
|     override fun imageUrlParse(document: Document) = "" | ||||
|  | ||||
|     private val completedFilter = Filter("Completed", "Completed") | ||||
|     // $("select[name=\"genres\"]").map((i,el) => `Filter("${i}", "${$(el).next().text().trim()}")`).get().join(',\n') | ||||
|     private class Status() : Filter.TriState("Completed") | ||||
|     private class Author() : Filter.Text("Author") | ||||
|     private class Genre(name: String, val id: Int) : Filter.TriState(name) | ||||
|  | ||||
|     // $("select[name=\"genres\"]").map((i,el) => `Genre("${$(el).next().text().trim()}", ${i})`).get().join(',\n') | ||||
|     // on http://kissmanga.com/AdvanceSearch | ||||
|     override fun getFilterList(): List<Filter> = listOf( | ||||
|             completedFilter, | ||||
|             Filter("0", "Action"), | ||||
|             Filter("1", "Adult"), | ||||
|             Filter("2", "Adventure"), | ||||
|             Filter("3", "Comedy"), | ||||
|             Filter("4", "Comic"), | ||||
|             Filter("5", "Cooking"), | ||||
|             Filter("6", "Doujinshi"), | ||||
|             Filter("7", "Drama"), | ||||
|             Filter("8", "Ecchi"), | ||||
|             Filter("9", "Fantasy"), | ||||
|             Filter("10", "Gender Bender"), | ||||
|             Filter("11", "Harem"), | ||||
|             Filter("12", "Historical"), | ||||
|             Filter("13", "Horror"), | ||||
|             Filter("14", "Josei"), | ||||
|             Filter("15", "Lolicon"), | ||||
|             Filter("16", "Manga"), | ||||
|             Filter("17", "Manhua"), | ||||
|             Filter("18", "Manhwa"), | ||||
|             Filter("19", "Martial Arts"), | ||||
|             Filter("20", "Mature"), | ||||
|             Filter("21", "Mecha"), | ||||
|             Filter("22", "Medical"), | ||||
|             Filter("23", "Music"), | ||||
|             Filter("24", "Mystery"), | ||||
|             Filter("25", "One shot"), | ||||
|             Filter("26", "Psychological"), | ||||
|             Filter("27", "Romance"), | ||||
|             Filter("28", "School Life"), | ||||
|             Filter("29", "Sci-fi"), | ||||
|             Filter("30", "Seinen"), | ||||
|             Filter("31", "Shotacon"), | ||||
|             Filter("32", "Shoujo"), | ||||
|             Filter("33", "Shoujo Ai"), | ||||
|             Filter("34", "Shounen"), | ||||
|             Filter("35", "Shounen Ai"), | ||||
|             Filter("36", "Slice of Life"), | ||||
|             Filter("37", "Smut"), | ||||
|             Filter("38", "Sports"), | ||||
|             Filter("39", "Supernatural"), | ||||
|             Filter("40", "Tragedy"), | ||||
|             Filter("41", "Webtoon"), | ||||
|             Filter("42", "Yaoi"), | ||||
|             Filter("43", "Yuri") | ||||
|     override fun getFilterList(): List<Filter<*>> = listOf( | ||||
|             Author(), | ||||
|             Status(), | ||||
|             Filter.Header("Genres"), | ||||
|             Genre("Action", 0), | ||||
|             Genre("Adult", 1), | ||||
|             Genre("Adventure", 2), | ||||
|             Genre("Comedy", 3), | ||||
|             Genre("Comic", 4), | ||||
|             Genre("Cooking", 5), | ||||
|             Genre("Doujinshi", 6), | ||||
|             Genre("Drama", 7), | ||||
|             Genre("Ecchi", 8), | ||||
|             Genre("Fantasy", 9), | ||||
|             Genre("Gender Bender", 10), | ||||
|             Genre("Harem", 11), | ||||
|             Genre("Historical", 12), | ||||
|             Genre("Horror", 13), | ||||
|             Genre("Josei", 14), | ||||
|             Genre("Lolicon", 15), | ||||
|             Genre("Manga", 16), | ||||
|             Genre("Manhua", 17), | ||||
|             Genre("Manhwa", 18), | ||||
|             Genre("Martial Arts", 19), | ||||
|             Genre("Mature", 20), | ||||
|             Genre("Mecha", 21), | ||||
|             Genre("Medical", 22), | ||||
|             Genre("Music", 23), | ||||
|             Genre("Mystery", 24), | ||||
|             Genre("One shot", 25), | ||||
|             Genre("Psychological", 26), | ||||
|             Genre("Romance", 27), | ||||
|             Genre("School Life", 28), | ||||
|             Genre("Sci-fi", 29), | ||||
|             Genre("Seinen", 30), | ||||
|             Genre("Shotacon", 31), | ||||
|             Genre("Shoujo", 32), | ||||
|             Genre("Shoujo Ai", 33), | ||||
|             Genre("Shounen", 34), | ||||
|             Genre("Shounen Ai", 35), | ||||
|             Genre("Slice of Life", 36), | ||||
|             Genre("Smut", 37), | ||||
|             Genre("Sports", 38), | ||||
|             Genre("Supernatural", 39), | ||||
|             Genre("Tragedy", 40), | ||||
|             Genre("Webtoon", 41), | ||||
|             Genre("Yaoi", 42), | ||||
|             Genre("Yuri", 43) | ||||
|     ) | ||||
| } | ||||
| @@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.data.source.model.Page | ||||
| import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource | ||||
| import eu.kanade.tachiyomi.util.asJsoup | ||||
| import okhttp3.HttpUrl | ||||
| import okhttp3.Response | ||||
| import org.jsoup.nodes.Document | ||||
| import org.jsoup.nodes.Element | ||||
| @@ -45,8 +46,18 @@ class Mangafox(override val id: Int) : ParsedOnlineSource() { | ||||
|  | ||||
|     override fun latestUpdatesNextPageSelector() = "a:has(span.next)" | ||||
|  | ||||
|     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("&")}" | ||||
|     override fun searchMangaInitialUrl(query: String, filters: List<Filter<*>>): String { | ||||
|         val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1").newBuilder().addQueryParameter("name", query) | ||||
|         for (filter in if (filters.isEmpty()) this@Mangafox.filters else filters) { | ||||
|             when (filter) { | ||||
|                 is Genre -> url.addQueryParameter(filter.id, filter.state.toString()) | ||||
|                 is TextField -> url.addQueryParameter(filter.key, filter.state) | ||||
|                 is ListField -> url.addQueryParameter(filter.key, filter.values[filter.state].value) | ||||
|                 is Order -> url.addQueryParameter("order", if (filter.state) "az" else "za") | ||||
|             } | ||||
|         } | ||||
|         return url.toString() | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaSelector() = "div#mangalist > ul.list > li" | ||||
|  | ||||
| @@ -123,49 +134,66 @@ class Mangafox(override val id: Int) : ParsedOnlineSource() { | ||||
|     } | ||||
|  | ||||
|     // Not used, overrides parent. | ||||
|     override fun pageListParse(document: Document, pages: MutableList<Page>) {} | ||||
|     override fun pageListParse(document: Document, pages: MutableList<Page>) { | ||||
|     } | ||||
|  | ||||
|     override fun imageUrlParse(document: Document) = document.getElementById("image").attr("src") | ||||
|  | ||||
|     // $('select.genres').map((i,el)=>`Filter("${$(el).attr('name')}", "${$(el).next().text().trim()}")`).get().join(',\n') | ||||
|     // on http://kissmanga.com/AdvanceSearch | ||||
|     override fun getFilterList(): List<Filter> = listOf( | ||||
|             Filter("is_completed", "Completed"), | ||||
|             Filter("genres[Action]", "Action"), | ||||
|             Filter("genres[Adult]", "Adult"), | ||||
|             Filter("genres[Adventure]", "Adventure"), | ||||
|             Filter("genres[Comedy]", "Comedy"), | ||||
|             Filter("genres[Doujinshi]", "Doujinshi"), | ||||
|             Filter("genres[Drama]", "Drama"), | ||||
|             Filter("genres[Ecchi]", "Ecchi"), | ||||
|             Filter("genres[Fantasy]", "Fantasy"), | ||||
|             Filter("genres[Gender Bender]", "Gender Bender"), | ||||
|             Filter("genres[Harem]", "Harem"), | ||||
|             Filter("genres[Historical]", "Historical"), | ||||
|             Filter("genres[Horror]", "Horror"), | ||||
|             Filter("genres[Josei]", "Josei"), | ||||
|             Filter("genres[Martial Arts]", "Martial Arts"), | ||||
|             Filter("genres[Mature]", "Mature"), | ||||
|             Filter("genres[Mecha]", "Mecha"), | ||||
|             Filter("genres[Mystery]", "Mystery"), | ||||
|             Filter("genres[One Shot]", "One Shot"), | ||||
|             Filter("genres[Psychological]", "Psychological"), | ||||
|             Filter("genres[Romance]", "Romance"), | ||||
|             Filter("genres[School Life]", "School Life"), | ||||
|             Filter("genres[Sci-fi]", "Sci-fi"), | ||||
|             Filter("genres[Seinen]", "Seinen"), | ||||
|             Filter("genres[Shoujo]", "Shoujo"), | ||||
|             Filter("genres[Shoujo Ai]", "Shoujo Ai"), | ||||
|             Filter("genres[Shounen]", "Shounen"), | ||||
|             Filter("genres[Shounen Ai]", "Shounen Ai"), | ||||
|             Filter("genres[Slice of Life]", "Slice of Life"), | ||||
|             Filter("genres[Smut]", "Smut"), | ||||
|             Filter("genres[Sports]", "Sports"), | ||||
|             Filter("genres[Supernatural]", "Supernatural"), | ||||
|             Filter("genres[Tragedy]", "Tragedy"), | ||||
|             Filter("genres[Webtoons]", "Webtoons"), | ||||
|             Filter("genres[Yaoi]", "Yaoi"), | ||||
|             Filter("genres[Yuri]", "Yuri") | ||||
|     private data class ListValue(val name: String, val value: String) { | ||||
|         override fun toString(): String = name | ||||
|     } | ||||
|  | ||||
|     private class Genre(name: String, val id: String = "genres[$name]") : Filter.TriState(name) | ||||
|     private class TextField(name: String, val key: String) : Filter.Text(name) | ||||
|     private class ListField(name: String, val key: String, values: Array<ListValue>, state: Int = 0) : Filter.List<ListValue>(name, values, state) | ||||
|     private class Order() : Filter.CheckBox("Ascending order") | ||||
|  | ||||
|     // $('select.genres').map((i,el)=>`Genre("${$(el).next().text().trim()}", "${$(el).attr('name')}")`).get().join(',\n') | ||||
|     // on http://mangafox.me/search.php | ||||
|     override fun getFilterList(): List<Filter<*>> = listOf( | ||||
|             TextField("Author", "author"), | ||||
|             TextField("Artist", "artist"), | ||||
|             ListField("Type", "type", arrayOf(ListValue("Any", ""), ListValue("Japanese Manga", "1"), ListValue("Korean Manhwa", "2"), ListValue("Chinese Manhua", "3"))), | ||||
|             Genre("Completed", "is_completed"), | ||||
|             Filter.Header(""), | ||||
|             ListField("Order by", "sort", arrayOf(ListValue("Series name", "name"), ListValue("Rating", "rating"), ListValue("Views", "views"), ListValue("Total chapters", "total_chapters"), ListValue("Last chapter", "last_chapter_time")), 2), | ||||
|             Order(), | ||||
|             Filter.Header("Genres"), | ||||
|             Genre("Action"), | ||||
|             Genre("Adult"), | ||||
|             Genre("Adventure"), | ||||
|             Genre("Comedy"), | ||||
|             Genre("Doujinshi"), | ||||
|             Genre("Drama"), | ||||
|             Genre("Ecchi"), | ||||
|             Genre("Fantasy"), | ||||
|             Genre("Gender Bender"), | ||||
|             Genre("Harem"), | ||||
|             Genre("Historical"), | ||||
|             Genre("Horror"), | ||||
|             Genre("Josei"), | ||||
|             Genre("Martial Arts"), | ||||
|             Genre("Mature"), | ||||
|             Genre("Mecha"), | ||||
|             Genre("Mystery"), | ||||
|             Genre("One Shot"), | ||||
|             Genre("Psychological"), | ||||
|             Genre("Romance"), | ||||
|             Genre("School Life"), | ||||
|             Genre("Sci-fi"), | ||||
|             Genre("Seinen"), | ||||
|             Genre("Shoujo"), | ||||
|             Genre("Shoujo Ai"), | ||||
|             Genre("Shounen"), | ||||
|             Genre("Shounen Ai"), | ||||
|             Genre("Slice of Life"), | ||||
|             Genre("Smut"), | ||||
|             Genre("Sports"), | ||||
|             Genre("Supernatural"), | ||||
|             Genre("Tragedy"), | ||||
|             Genre("Webtoons"), | ||||
|             Genre("Yaoi"), | ||||
|             Genre("Yuri") | ||||
|     ) | ||||
|  | ||||
| } | ||||
| @@ -4,6 +4,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.data.source.model.Page | ||||
| import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource | ||||
| import okhttp3.HttpUrl | ||||
| import org.jsoup.nodes.Document | ||||
| import org.jsoup.nodes.Element | ||||
| import java.text.ParseException | ||||
| @@ -47,7 +48,20 @@ class Mangahere(override val id: Int) : ParsedOnlineSource() { | ||||
|  | ||||
|     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<*>>): String { | ||||
|         val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1").newBuilder().addQueryParameter("name", query) | ||||
|         for (filter in if (filters.isEmpty()) this@Mangahere.filters else filters) { | ||||
|             when (filter) { | ||||
|                 is Status -> url.addQueryParameter("is_completed", arrayOf("", "1", "0")[filter.state]) | ||||
|                 is Genre -> url.addQueryParameter(filter.id, filter.state.toString()) | ||||
|                 is TextField -> url.addQueryParameter(filter.key, filter.state) | ||||
|                 is ListField -> url.addQueryParameter(filter.key, filter.values[filter.state].value) | ||||
|                 is Order -> url.addQueryParameter("order", if (filter.state) "az" else "za") | ||||
|             } | ||||
|         } | ||||
|         return url.toString() | ||||
|     } | ||||
|  | ||||
|  | ||||
|     override fun searchMangaSelector() = "div.result_search > dl:has(dt)" | ||||
|  | ||||
| @@ -82,12 +96,12 @@ class Mangahere(override val id: Int) : ParsedOnlineSource() { | ||||
|  | ||||
|         val urlElement = parentEl.select("a").first() | ||||
|  | ||||
|         var volume = parentEl.select("span.mr6")?.first()?.text()?.trim()?:"" | ||||
|         var volume = parentEl.select("span.mr6")?.first()?.text()?.trim() ?: "" | ||||
|         if (volume.length > 0) { | ||||
|             volume = " - " + volume | ||||
|         } | ||||
|  | ||||
|         var title = parentEl?.textNodes()?.last()?.text()?.trim()?:"" | ||||
|         var title = parentEl?.textNodes()?.last()?.text()?.trim() ?: "" | ||||
|         if (title.length > 0) { | ||||
|             title = " - " + title | ||||
|         } | ||||
| @@ -131,42 +145,59 @@ class Mangahere(override val id: Int) : ParsedOnlineSource() { | ||||
|  | ||||
|     override fun imageUrlParse(document: Document) = document.getElementById("image").attr("src") | ||||
|  | ||||
|     // [...document.querySelectorAll("select[id^='genres'")].map((el,i) => `Filter("${el.getAttribute('name')}", "${el.nextSibling.nextSibling.textContent.trim()}")`).join(',\n') | ||||
|     private data class ListValue(val name: String, val value: String) { | ||||
|         override fun toString(): String = name | ||||
|     } | ||||
|  | ||||
|     private class Status() : Filter.TriState("Completed") | ||||
|     private class Genre(name: String, val id: String = "genres[$name]") : Filter.TriState(name) | ||||
|     private class TextField(name: String, val key: String) : Filter.Text(name) | ||||
|     private class ListField(name: String, val key: String, values: Array<ListValue>, state: Int = 0) : Filter.List<ListValue>(name, values, state) | ||||
|     private class Order() : Filter.CheckBox("Ascending order") | ||||
|  | ||||
|     // [...document.querySelectorAll("select[id^='genres'")].map((el,i) => `Genre("${el.nextSibling.nextSibling.textContent.trim()}", "${el.getAttribute('name')}")`).join(',\n') | ||||
|     // http://www.mangahere.co/advsearch.htm | ||||
|     override fun getFilterList(): List<Filter> = listOf( | ||||
|             Filter("is_completed", "Completed"), | ||||
|             Filter("genres[Action]", "Action"), | ||||
|             Filter("genres[Adventure]", "Adventure"), | ||||
|             Filter("genres[Comedy]", "Comedy"), | ||||
|             Filter("genres[Doujinshi]", "Doujinshi"), | ||||
|             Filter("genres[Drama]", "Drama"), | ||||
|             Filter("genres[Ecchi]", "Ecchi"), | ||||
|             Filter("genres[Fantasy]", "Fantasy"), | ||||
|             Filter("genres[Gender Bender]", "Gender Bender"), | ||||
|             Filter("genres[Harem]", "Harem"), | ||||
|             Filter("genres[Historical]", "Historical"), | ||||
|             Filter("genres[Horror]", "Horror"), | ||||
|             Filter("genres[Josei]", "Josei"), | ||||
|             Filter("genres[Martial Arts]", "Martial Arts"), | ||||
|             Filter("genres[Mature]", "Mature"), | ||||
|             Filter("genres[Mecha]", "Mecha"), | ||||
|             Filter("genres[Mystery]", "Mystery"), | ||||
|             Filter("genres[One Shot]", "One Shot"), | ||||
|             Filter("genres[Psychological]", "Psychological"), | ||||
|             Filter("genres[Romance]", "Romance"), | ||||
|             Filter("genres[School Life]", "School Life"), | ||||
|             Filter("genres[Sci-fi]", "Sci-fi"), | ||||
|             Filter("genres[Seinen]", "Seinen"), | ||||
|             Filter("genres[Shoujo]", "Shoujo"), | ||||
|             Filter("genres[Shoujo Ai]", "Shoujo Ai"), | ||||
|             Filter("genres[Shounen]", "Shounen"), | ||||
|             Filter("genres[Shounen Ai]", "Shounen Ai"), | ||||
|             Filter("genres[Slice of Life]", "Slice of Life"), | ||||
|             Filter("genres[Sports]", "Sports"), | ||||
|             Filter("genres[Supernatural]", "Supernatural"), | ||||
|             Filter("genres[Tragedy]", "Tragedy"), | ||||
|             Filter("genres[Yaoi]", "Yaoi"), | ||||
|             Filter("genres[Yuri]", "Yuri") | ||||
|     override fun getFilterList(): List<Filter<*>> = listOf( | ||||
|             TextField("Author", "author"), | ||||
|             TextField("Artist", "artist"), | ||||
|             ListField("Type", "direction", arrayOf(ListValue("Any", ""), ListValue("Japanese Manga (read from right to left)", "rl"), ListValue("Korean Manhwa (read from left to right)", "lr"))), | ||||
|             Status(), | ||||
|             Filter.Header(""), | ||||
|             ListField("Order by", "sort", arrayOf(ListValue("Series name", "name"), ListValue("Rating", "rating"), ListValue("Views", "views"), ListValue("Total chapters", "total_chapters"), ListValue("Last chapter", "last_chapter_time")), 2), | ||||
|             Order(), | ||||
|             Filter.Header("Genres"), | ||||
|             Genre("Action"), | ||||
|             Genre("Adventure"), | ||||
|             Genre("Comedy"), | ||||
|             Genre("Doujinshi"), | ||||
|             Genre("Drama"), | ||||
|             Genre("Ecchi"), | ||||
|             Genre("Fantasy"), | ||||
|             Genre("Gender Bender"), | ||||
|             Genre("Harem"), | ||||
|             Genre("Historical"), | ||||
|             Genre("Horror"), | ||||
|             Genre("Josei"), | ||||
|             Genre("Martial Arts"), | ||||
|             Genre("Mature"), | ||||
|             Genre("Mecha"), | ||||
|             Genre("Mystery"), | ||||
|             Genre("One Shot"), | ||||
|             Genre("Psychological"), | ||||
|             Genre("Romance"), | ||||
|             Genre("School Life"), | ||||
|             Genre("Sci-fi"), | ||||
|             Genre("Seinen"), | ||||
|             Genre("Shoujo"), | ||||
|             Genre("Shoujo Ai"), | ||||
|             Genre("Shounen"), | ||||
|             Genre("Shounen Ai"), | ||||
|             Genre("Slice of Life"), | ||||
|             Genre("Sports"), | ||||
|             Genre("Supernatural"), | ||||
|             Genre("Tragedy"), | ||||
|             Genre("Yaoi"), | ||||
|             Genre("Yuri") | ||||
|     ) | ||||
|  | ||||
| } | ||||
| @@ -30,7 +30,7 @@ class Mangasee(override val id: Int) : ParsedOnlineSource() { | ||||
|  | ||||
|     private val indexPattern = Pattern.compile("-index-(.*?)-") | ||||
|  | ||||
|     override fun popularMangaInitialUrl() = "$baseUrl/search/request.php?sortBy=popularity&sortOrder=descending" | ||||
|     override fun popularMangaInitialUrl() = "$baseUrl/search/request.php?sortBy=popularity&sortOrder=descending&todo=1" | ||||
|  | ||||
|     override fun popularMangaSelector() = "div.requested > div.row" | ||||
|  | ||||
| @@ -64,20 +64,32 @@ class Mangasee(override val id: Int) : ParsedOnlineSource() { | ||||
|     // Not used, overrides parent. | ||||
|     override fun popularMangaNextPageSelector() = "" | ||||
|  | ||||
|     override fun searchMangaInitialUrl(query: String, filters: List<Filter>): String { | ||||
|         var url = "$baseUrl/search/request.php?sortBy=popularity&sortOrder=descending&keyword=$query" | ||||
|     override fun searchMangaInitialUrl(query: String, filters: List<Filter<*>>): String { | ||||
|         val url = HttpUrl.parse("$baseUrl/search/request.php").newBuilder() | ||||
|         if (!query.isEmpty()) url.addQueryParameter("keyword", query) | ||||
|         var genres: String? = null | ||||
|         for (filter in filters) { | ||||
|             if (filter.equals(completedFilter)) url += "&status=Complete" | ||||
|             else if (genres == null) genres = filter.id | ||||
|             else genres += "," + filter.id | ||||
|         var genresNo: String? = null | ||||
|         for (filter in if (filters.isEmpty()) this@Mangasee.filters else filters) { | ||||
|             when (filter) { | ||||
|                 is Sort -> filter.values[filter.state].keys.forEachIndexed { i, s -> | ||||
|                     url.addQueryParameter(s, filter.values[filter.state].values[i]) | ||||
|                 } | ||||
|                 is ListField -> if (filter.state != 0) url.addQueryParameter(filter.key, filter.values[filter.state]) | ||||
|                 is TextField -> if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state) | ||||
|                 is Genre -> when (filter.state) { | ||||
|                     Filter.TriState.STATE_INCLUDE -> genres = if (genres == null) filter.id else genres + "," + filter.id | ||||
|                     Filter.TriState.STATE_EXCLUDE -> genresNo = if (genresNo == null) filter.id else genresNo + "," + filter.id | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return if (genres == null) url else url + "&genre=$genres" | ||||
|         if (genres != null) url.addQueryParameter("genre", genres) | ||||
|         if (genresNo != null) url.addQueryParameter("genreNo", genresNo) | ||||
|         return url.toString() | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaSelector() = "div.searchResults > div.requested > div.row" | ||||
|  | ||||
|     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) { | ||||
|             page.url = searchMangaInitialUrl(query, filters) | ||||
|         } | ||||
| @@ -95,7 +107,7 @@ class Mangasee(override val id: Int) : ParsedOnlineSource() { | ||||
|         return Pair(body, requestUrl) | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List<Filter>) { | ||||
|     override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List<Filter<*>>) { | ||||
|         val document = response.asJsoup() | ||||
|         for (element in document.select(popularMangaSelector())) { | ||||
|             Manga.create(id).apply { | ||||
| @@ -174,47 +186,67 @@ class Mangasee(override val id: Int) : ParsedOnlineSource() { | ||||
|  | ||||
|     override fun imageUrlParse(document: Document): String = document.select("img.CurImage").attr("src") | ||||
|  | ||||
|     private val completedFilter = Filter("Complete", "Completed") | ||||
|     private data class SortOption(val name: String, val keys: Array<String>, val values: Array<String>) { | ||||
|         override fun toString(): String = name | ||||
|     } | ||||
|  | ||||
|     private class Sort(name: String, values: Array<SortOption>, state: Int = 0) : Filter.List<SortOption>(name, values, state) | ||||
|     private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name) | ||||
|     private class TextField(name: String, val key: String) : Filter.Text(name) | ||||
|     private class ListField(name: String, val key: String, values: Array<String>, state: Int = 0) : Filter.List<String>(name, values, state) | ||||
|  | ||||
|     // [...document.querySelectorAll("label.triStateCheckBox input")].map(el => `Filter("${el.getAttribute('name')}", "${el.nextSibling.textContent.trim()}")`).join(',\n') | ||||
|     // http://mangasee.co/advanced-search/ | ||||
|     override fun getFilterList(): List<Filter> = listOf( | ||||
|             completedFilter, | ||||
|             Filter("Action", "Action"), | ||||
|             Filter("Adult", "Adult"), | ||||
|             Filter("Adventure", "Adventure"), | ||||
|             Filter("Comedy", "Comedy"), | ||||
|             Filter("Doujinshi", "Doujinshi"), | ||||
|             Filter("Drama", "Drama"), | ||||
|             Filter("Ecchi", "Ecchi"), | ||||
|             Filter("Fantasy", "Fantasy"), | ||||
|             Filter("Gender_Bender", "Gender Bender"), | ||||
|             Filter("Harem", "Harem"), | ||||
|             Filter("Hentai", "Hentai"), | ||||
|             Filter("Historical", "Historical"), | ||||
|             Filter("Horror", "Horror"), | ||||
|             Filter("Josei", "Josei"), | ||||
|             Filter("Lolicon", "Lolicon"), | ||||
|             Filter("Martial_Arts", "Martial Arts"), | ||||
|             Filter("Mature", "Mature"), | ||||
|             Filter("Mecha", "Mecha"), | ||||
|             Filter("Mystery", "Mystery"), | ||||
|             Filter("Psychological", "Psychological"), | ||||
|             Filter("Romance", "Romance"), | ||||
|             Filter("School_Life", "School Life"), | ||||
|             Filter("Sci-fi", "Sci-fi"), | ||||
|             Filter("Seinen", "Seinen"), | ||||
|             Filter("Shotacon", "Shotacon"), | ||||
|             Filter("Shoujo", "Shoujo"), | ||||
|             Filter("Shoujo_Ai", "Shoujo Ai"), | ||||
|             Filter("Shounen", "Shounen"), | ||||
|             Filter("Shounen_Ai", "Shounen Ai"), | ||||
|             Filter("Slice_of_Life", "Slice of Life"), | ||||
|             Filter("Smut", "Smut"), | ||||
|             Filter("Sports", "Sports"), | ||||
|             Filter("Supernatural", "Supernatural"), | ||||
|             Filter("Tragedy", "Tragedy"), | ||||
|             Filter("Yaoi", "Yaoi"), | ||||
|             Filter("Yuri", "Yuri") | ||||
|     override fun getFilterList(): List<Filter<*>> = listOf( | ||||
|             TextField("Years", "year"), | ||||
|             TextField("Author", "author"), | ||||
|             Sort("Sort By", arrayOf(SortOption("Alphabetical A-Z", emptyArray(), emptyArray()), | ||||
|                     SortOption("Alphabetical Z-A", arrayOf("sortOrder"), arrayOf("descending")), | ||||
|                     SortOption("Newest", arrayOf("sortBy", "sortOrder"), arrayOf("dateUpdated", "descending")), | ||||
|                     SortOption("Oldest", arrayOf("sortBy"), arrayOf("dateUpdated")), | ||||
|                     SortOption("Most Popular", arrayOf("sortBy", "sortOrder"), arrayOf("popularity", "descending")), | ||||
|                     SortOption("Least Popular", arrayOf("sortBy"), arrayOf("popularity")) | ||||
|             ), 4), | ||||
|             ListField("Scan Status", "status", arrayOf("Any", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing")), | ||||
|             ListField("Publish Status", "pstatus", arrayOf("Any", "Cancelled", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing", "Unfinished")), | ||||
|             ListField("Type", "type", arrayOf("Any", "Doujinshi", "Manga", "Manhua", "Manhwa", "OEL", "One-shot")), | ||||
|             Filter.Header("Genres"), | ||||
|             Genre("Action"), | ||||
|             Genre("Adult"), | ||||
|             Genre("Adventure"), | ||||
|             Genre("Comedy"), | ||||
|             Genre("Doujinshi"), | ||||
|             Genre("Drama"), | ||||
|             Genre("Ecchi"), | ||||
|             Genre("Fantasy"), | ||||
|             Genre("Gender Bender"), | ||||
|             Genre("Harem"), | ||||
|             Genre("Hentai"), | ||||
|             Genre("Historical"), | ||||
|             Genre("Horror"), | ||||
|             Genre("Josei"), | ||||
|             Genre("Lolicon"), | ||||
|             Genre("Martial Arts"), | ||||
|             Genre("Mature"), | ||||
|             Genre("Mecha"), | ||||
|             Genre("Mystery"), | ||||
|             Genre("Psychological"), | ||||
|             Genre("Romance"), | ||||
|             Genre("School Life"), | ||||
|             Genre("Sci-fi"), | ||||
|             Genre("Seinen"), | ||||
|             Genre("Shotacon"), | ||||
|             Genre("Shoujo"), | ||||
|             Genre("Shoujo Ai"), | ||||
|             Genre("Shounen"), | ||||
|             Genre("Shounen Ai"), | ||||
|             Genre("Slice of Life"), | ||||
|             Genre("Smut"), | ||||
|             Genre("Sports"), | ||||
|             Genre("Supernatural"), | ||||
|             Genre("Tragedy"), | ||||
|             Genre("Yaoi"), | ||||
|             Genre("Yuri") | ||||
|     ) | ||||
|  | ||||
|     override fun latestUpdatesInitialUrl(): String = "http://mangaseeonline.net/home/latest.request.php" | ||||
|   | ||||
| @@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.data.network.POST | ||||
| import eu.kanade.tachiyomi.data.source.model.MangasPage | ||||
| import eu.kanade.tachiyomi.data.source.model.Page | ||||
| import eu.kanade.tachiyomi.data.source.online.OnlineSource | ||||
| import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource | ||||
| import okhttp3.Headers | ||||
| import okhttp3.OkHttpClient | ||||
| @@ -57,25 +56,29 @@ class Readmangatoday(override val id: Int) : ParsedOnlineSource() { | ||||
|  | ||||
|     override fun latestUpdatesNextPageSelector(): String = "div.hot-manga > ul.pagination > li > a:contains(»)" | ||||
|  | ||||
|     override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = | ||||
|     override fun searchMangaInitialUrl(query: String, filters: List<Filter<*>>) = | ||||
|             "$baseUrl/service/advanced_search" | ||||
|  | ||||
|  | ||||
|     override fun searchMangaRequest(page: MangasPage, query: String, filters: List<OnlineSource.Filter>): Request { | ||||
|     override fun searchMangaRequest(page: MangasPage, query: String, filters: List<Filter<*>>): Request { | ||||
|         if (page.page == 1) { | ||||
|             page.url = searchMangaInitialUrl(query, filters) | ||||
|         } | ||||
|  | ||||
|         val builder = okhttp3.FormBody.Builder() | ||||
|         builder.add("manga-name", query) | ||||
|         builder.add("type", "all") | ||||
|         var status = "both" | ||||
|         for (filter in filters) { | ||||
|             if (filter.equals(completedFilter)) status = filter.id | ||||
|             else builder.add("include[]", filter.id) | ||||
|         } | ||||
|         builder.add("status", status) | ||||
|         for (filter in if (filters.isEmpty()) this@Readmangatoday.filters else filters) { | ||||
|             when (filter) { | ||||
|                 is TextField -> builder.add(filter.key, filter.state) | ||||
|                 is Type -> builder.add("type", arrayOf("all", "japanese", "korean", "chinese")[filter.state]) | ||||
|                 is Status -> builder.add("status", arrayOf("both", "completed", "ongoing")[filter.state]) | ||||
|                 is Genre -> when (filter.state) { | ||||
|                     Filter.TriState.STATE_INCLUDE -> builder.add("include[]", filter.id.toString()) | ||||
|                     Filter.TriState.STATE_EXCLUDE -> builder.add("exclude[]", filter.id.toString()) | ||||
|  | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return POST(page.url, headers, builder.build()) | ||||
|     } | ||||
|  | ||||
| @@ -118,16 +121,16 @@ class Readmangatoday(override val id: Int) : ParsedOnlineSource() { | ||||
|     } | ||||
|  | ||||
|     private fun parseChapterDate(date: String): Long { | ||||
|         val dateWords : List<String> = date.split(" ") | ||||
|         val dateWords: List<String> = date.split(" ") | ||||
|  | ||||
|         if (dateWords.size == 3) { | ||||
|             val timeAgo = Integer.parseInt(dateWords[0]) | ||||
|             var date : Calendar = Calendar.getInstance() | ||||
|             var date: Calendar = Calendar.getInstance() | ||||
|  | ||||
|             if (dateWords[1].contains("Minute")) { | ||||
|                 date.add(Calendar.MINUTE, - timeAgo) | ||||
|                 date.add(Calendar.MINUTE, -timeAgo) | ||||
|             } else if (dateWords[1].contains("Hour")) { | ||||
|                 date.add(Calendar.HOUR_OF_DAY, - timeAgo) | ||||
|                 date.add(Calendar.HOUR_OF_DAY, -timeAgo) | ||||
|             } else if (dateWords[1].contains("Day")) { | ||||
|                 date.add(Calendar.DAY_OF_YEAR, -timeAgo) | ||||
|             } else if (dateWords[1].contains("Week")) { | ||||
| @@ -153,45 +156,53 @@ class Readmangatoday(override val id: Int) : ParsedOnlineSource() { | ||||
|  | ||||
|     override fun imageUrlParse(document: Document) = document.select("img.img-responsive-2").first().attr("src") | ||||
|  | ||||
|     private val completedFilter = Filter("completed", "Completed") | ||||
|     // [...document.querySelectorAll("ul.manga-cat span")].map(el => `Filter("${el.getAttribute('data-id')}", "${el.nextSibling.textContent.trim()}")`).join(',\n') | ||||
|     private class Status() : Filter.TriState("Completed") | ||||
|     private class Genre(name: String, val id: Int) : Filter.TriState(name) | ||||
|     private class TextField(name: String, val key: String) : Filter.Text(name) | ||||
|     private class Type() : Filter.List<String>("Type", arrayOf("All", "Japanese Manga", "Korean Manhwa", "Chinese Manhua")) | ||||
|  | ||||
|     // [...document.querySelectorAll("ul.manga-cat span")].map(el => `Genre("${el.nextSibling.textContent.trim()}", ${el.getAttribute('data-id')})`).join(',\n') | ||||
|     // http://www.readmanga.today/advanced-search | ||||
|     override fun getFilterList(): List<Filter> = listOf( | ||||
|             completedFilter, | ||||
|             Filter("2", "Action"), | ||||
|             Filter("4", "Adventure"), | ||||
|             Filter("5", "Comedy"), | ||||
|             Filter("6", "Doujinshi"), | ||||
|             Filter("7", "Drama"), | ||||
|             Filter("8", "Ecchi"), | ||||
|             Filter("9", "Fantasy"), | ||||
|             Filter("10", "Gender Bender"), | ||||
|             Filter("11", "Harem"), | ||||
|             Filter("12", "Historical"), | ||||
|             Filter("13", "Horror"), | ||||
|             Filter("14", "Josei"), | ||||
|             Filter("15", "Lolicon"), | ||||
|             Filter("16", "Martial Arts"), | ||||
|             Filter("17", "Mature"), | ||||
|             Filter("18", "Mecha"), | ||||
|             Filter("19", "Mystery"), | ||||
|             Filter("20", "One shot"), | ||||
|             Filter("21", "Psychological"), | ||||
|             Filter("22", "Romance"), | ||||
|             Filter("23", "School Life"), | ||||
|             Filter("24", "Sci-fi"), | ||||
|             Filter("25", "Seinen"), | ||||
|             Filter("26", "Shotacon"), | ||||
|             Filter("27", "Shoujo"), | ||||
|             Filter("28", "Shoujo Ai"), | ||||
|             Filter("29", "Shounen"), | ||||
|             Filter("30", "Shounen Ai"), | ||||
|             Filter("31", "Slice of Life"), | ||||
|             Filter("32", "Smut"), | ||||
|             Filter("33", "Sports"), | ||||
|             Filter("34", "Supernatural"), | ||||
|             Filter("35", "Tragedy"), | ||||
|             Filter("36", "Yaoi"), | ||||
|             Filter("37", "Yuri") | ||||
|     override fun getFilterList(): List<Filter<*>> = listOf( | ||||
|             TextField("Author", "author-name"), | ||||
|             TextField("Artist", "artist-name"), | ||||
|             Type(), | ||||
|             Status(), | ||||
|             Filter.Header("Genres"), | ||||
|             Genre("Action", 2), | ||||
|             Genre("Adventure", 4), | ||||
|             Genre("Comedy", 5), | ||||
|             Genre("Doujinshi", 6), | ||||
|             Genre("Drama", 7), | ||||
|             Genre("Ecchi", 8), | ||||
|             Genre("Fantasy", 9), | ||||
|             Genre("Gender Bender", 10), | ||||
|             Genre("Harem", 11), | ||||
|             Genre("Historical", 12), | ||||
|             Genre("Horror", 13), | ||||
|             Genre("Josei", 14), | ||||
|             Genre("Lolicon", 15), | ||||
|             Genre("Martial Arts", 16), | ||||
|             Genre("Mature", 17), | ||||
|             Genre("Mecha", 18), | ||||
|             Genre("Mystery", 19), | ||||
|             Genre("One shot", 20), | ||||
|             Genre("Psychological", 21), | ||||
|             Genre("Romance", 22), | ||||
|             Genre("School Life", 23), | ||||
|             Genre("Sci-fi", 24), | ||||
|             Genre("Seinen", 25), | ||||
|             Genre("Shotacon", 26), | ||||
|             Genre("Shoujo", 27), | ||||
|             Genre("Shoujo Ai", 28), | ||||
|             Genre("Shounen", 29), | ||||
|             Genre("Shounen Ai", 30), | ||||
|             Genre("Slice of Life", 31), | ||||
|             Genre("Smut", 32), | ||||
|             Genre("Sports", 33), | ||||
|             Genre("Supernatural", 34), | ||||
|             Genre("Tragedy", 35), | ||||
|             Genre("Yaoi", 36), | ||||
|             Genre("Yuri", 37) | ||||
|     ) | ||||
| } | ||||
| @@ -45,7 +45,7 @@ class WieManga(override val id: Int) : ParsedOnlineSource() { | ||||
|  | ||||
|     override fun latestUpdatesNextPageSelector() = null | ||||
|  | ||||
|     override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = "$baseUrl/search/?wd=$query" | ||||
|     override fun searchMangaInitialUrl(query: String, filters: List<Filter<*>>) = "$baseUrl/search/?wd=$query" | ||||
|  | ||||
|     override fun searchMangaSelector() = ".searchresult td > div" | ||||
|  | ||||
| @@ -70,10 +70,10 @@ class WieManga(override val id: Int) : ParsedOnlineSource() { | ||||
|         manga.thumbnail_url = imageElement.select("img").first()?.attr("src") | ||||
|  | ||||
|         if (manga.author == "RSS") | ||||
|                 manga.author = null | ||||
|             manga.author = null | ||||
|  | ||||
|         if (manga.artist == "RSS") | ||||
|                 manga.artist = null | ||||
|             manga.artist = null | ||||
|     } | ||||
|  | ||||
|     override fun chapterListSelector() = ".chapterlist tr:not(:first-child)" | ||||
| @@ -95,11 +95,12 @@ class WieManga(override val id: Int) : ParsedOnlineSource() { | ||||
|         val document = response.asJsoup() | ||||
|  | ||||
|         document.select("select#page").first().select("option").forEach { | ||||
|                 pages.add(Page(pages.size, it.attr("value"))) | ||||
|             pages.add(Page(pages.size, it.attr("value"))) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun pageListParse(document: Document, pages: MutableList<Page>) {} | ||||
|     override fun pageListParse(document: Document, pages: MutableList<Page>) { | ||||
|     } | ||||
|  | ||||
|     override fun imageUrlParse(document: Document) = document.select("img#comicpic").first().attr("src") | ||||
|  | ||||
|   | ||||
| @@ -26,15 +26,18 @@ class Mangachan(override val id: Int) : ParsedOnlineSource() { | ||||
|  | ||||
|     override fun latestUpdatesInitialUrl() = "$baseUrl/newestch" | ||||
|  | ||||
|     override fun searchMangaInitialUrl(query: String, filters: List<Filter>): String { | ||||
|     override fun searchMangaInitialUrl(query: String, filters: List<Filter<*>>): String { | ||||
|         if (query.isNotEmpty()) { | ||||
|             return "$baseUrl/?do=search&subaction=search&story=$query" | ||||
|         } else if (filters.isNotEmpty()) { | ||||
|             var genres = "" | ||||
|             filters.forEach { genres = genres + it.name + '+' } | ||||
|             return "$baseUrl/tags/${genres.dropLast(1)}" | ||||
|         } else { | ||||
|             return "$baseUrl/?do=search&subaction=search&story=$query" | ||||
|             val filt = filters.filter { it.state != Filter.TriState.STATE_IGNORE } | ||||
|             if (filt.isNotEmpty()) { | ||||
|                 var genres = "" | ||||
|                 filt.forEach { genres += (if (it.state == Filter.TriState.STATE_EXCLUDE) "-" else "") + (it as Genre).id + '+' } | ||||
|                 return "$baseUrl/tags/${genres.dropLast(1)}" | ||||
|             } else { | ||||
|                 return "$baseUrl/?do=search&subaction=search&story=$query" | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -70,7 +73,7 @@ class Mangachan(override val id: Int) : ParsedOnlineSource() { | ||||
|  | ||||
|     private fun searchGenresNextPageSelector() = popularMangaNextPageSelector() | ||||
|  | ||||
|     override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List<Filter>) { | ||||
|     override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List<Filter<*>>) { | ||||
|         val document = response.asJsoup() | ||||
|         for (element in document.select(searchMangaSelector())) { | ||||
|             Manga.create(id).apply { | ||||
| @@ -78,9 +81,9 @@ class Mangachan(override val id: Int) : ParsedOnlineSource() { | ||||
|                 page.mangas.add(this) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         val allIgnore = filters.all { it.state == Filter.TriState.STATE_IGNORE } | ||||
|         searchMangaNextPageSelector().let { selector -> | ||||
|             if (page.nextPageUrl.isNullOrEmpty() && filters.isEmpty()) { | ||||
|             if (page.nextPageUrl.isNullOrEmpty() && allIgnore) { | ||||
|                 val onClick = document.select(selector).first()?.attr("onclick") | ||||
|                 val pageNum = onClick?.substring(23, onClick.indexOf("); return(false)")) | ||||
|                 page.nextPageUrl = searchMangaInitialUrl(query, emptyList()) + "&search_start=" + pageNum | ||||
| @@ -88,7 +91,7 @@ class Mangachan(override val id: Int) : ParsedOnlineSource() { | ||||
|         } | ||||
|  | ||||
|         searchGenresNextPageSelector().let { selector -> | ||||
|             if (page.nextPageUrl.isNullOrEmpty() && filters.isNotEmpty()) { | ||||
|             if (page.nextPageUrl.isNullOrEmpty() && !allIgnore) { | ||||
|                 val url = document.select(selector).first()?.attr("href") | ||||
|                 page.nextPageUrl = searchMangaInitialUrl(query, filters) + url | ||||
|             } | ||||
| @@ -137,71 +140,75 @@ class Mangachan(override val id: Int) : ParsedOnlineSource() { | ||||
|         pageUrls.mapIndexedTo(pages) { i, url -> Page(i, "", url) } | ||||
|     } | ||||
|  | ||||
|     override fun pageListParse(document: Document, pages: MutableList<Page>) { } | ||||
|     override fun pageListParse(document: Document, pages: MutableList<Page>) { | ||||
|     } | ||||
|  | ||||
|     override fun imageUrlParse(document: Document) = "" | ||||
|  | ||||
|     private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name) | ||||
|  | ||||
|     /* [...document.querySelectorAll("li.sidetag > a:nth-child(1)")].map((el,i) => | ||||
|     *  { const link=el.getAttribute('href');const id=link.substr(6,link.length); | ||||
|     *  return `Filter("${id}", "${id}")` }).join(',\n') | ||||
|     *  return `Genre("${id.replace("_", " ")}")` }).join(',\n') | ||||
|     *  on http://mangachan.me/ | ||||
|     */ | ||||
|     override fun getFilterList(): List<Filter> = listOf( | ||||
|             Filter("18_плюс", "18_плюс"), | ||||
|             Filter("bdsm", "bdsm"), | ||||
|             Filter("арт", "арт"), | ||||
|             Filter("биография", "биография"), | ||||
|             Filter("боевик", "боевик"), | ||||
|             Filter("боевые_искусства", "боевые_искусства"), | ||||
|             Filter("вампиры", "вампиры"), | ||||
|             Filter("веб", "веб"), | ||||
|             Filter("гарем", "гарем"), | ||||
|             Filter("гендерная_интрига", "гендерная_интрига"), | ||||
|             Filter("героическое_фэнтези", "героическое_фэнтези"), | ||||
|             Filter("детектив", "детектив"), | ||||
|             Filter("дзёсэй", "дзёсэй"), | ||||
|             Filter("додзинси", "додзинси"), | ||||
|             Filter("драма", "драма"), | ||||
|             Filter("игра", "игра"), | ||||
|             Filter("инцест", "инцест"), | ||||
|             Filter("искусство", "искусство"), | ||||
|             Filter("история", "история"), | ||||
|             Filter("киберпанк", "киберпанк"), | ||||
|             Filter("кодомо", "кодомо"), | ||||
|             Filter("комедия", "комедия"), | ||||
|             Filter("литРПГ", "литРПГ"), | ||||
|             Filter("махо-сёдзё", "махо-сёдзё"), | ||||
|             Filter("меха", "меха"), | ||||
|             Filter("мистика", "мистика"), | ||||
|             Filter("музыка", "музыка"), | ||||
|             Filter("научная_фантастика", "научная_фантастика"), | ||||
|             Filter("повседневность", "повседневность"), | ||||
|             Filter("постапокалиптика", "постапокалиптика"), | ||||
|             Filter("приключения", "приключения"), | ||||
|             Filter("психология", "психология"), | ||||
|             Filter("романтика", "романтика"), | ||||
|             Filter("самурайский_боевик", "самурайский_боевик"), | ||||
|             Filter("сборник", "сборник"), | ||||
|             Filter("сверхъестественное", "сверхъестественное"), | ||||
|             Filter("сказка", "сказка"), | ||||
|             Filter("спорт", "спорт"), | ||||
|             Filter("супергерои", "супергерои"), | ||||
|             Filter("сэйнэн", "сэйнэн"), | ||||
|             Filter("сёдзё", "сёдзё"), | ||||
|             Filter("сёдзё-ай", "сёдзё-ай"), | ||||
|             Filter("сёнэн", "сёнэн"), | ||||
|             Filter("сёнэн-ай", "сёнэн-ай"), | ||||
|             Filter("тентакли", "тентакли"), | ||||
|             Filter("трагедия", "трагедия"), | ||||
|             Filter("триллер", "триллер"), | ||||
|             Filter("ужасы", "ужасы"), | ||||
|             Filter("фантастика", "фантастика"), | ||||
|             Filter("фурри", "фурри"), | ||||
|             Filter("фэнтези", "фэнтези"), | ||||
|             Filter("школа", "школа"), | ||||
|             Filter("эротика", "эротика"), | ||||
|             Filter("юри", "юри"), | ||||
|             Filter("яой", "яой"), | ||||
|             Filter("ёнкома", "ёнкома") | ||||
|     override fun getFilterList(): List<Filter<*>> = listOf( | ||||
|             Genre("18 плюс"), | ||||
|             Genre("bdsm"), | ||||
|             Genre("арт"), | ||||
|             Genre("биография"), | ||||
|             Genre("боевик"), | ||||
|             Genre("боевые искусства"), | ||||
|             Genre("вампиры"), | ||||
|             Genre("веб"), | ||||
|             Genre("гарем"), | ||||
|             Genre("гендерная интрига"), | ||||
|             Genre("героическое фэнтези"), | ||||
|             Genre("детектив"), | ||||
|             Genre("дзёсэй"), | ||||
|             Genre("додзинси"), | ||||
|             Genre("драма"), | ||||
|             Genre("игра"), | ||||
|             Genre("инцест"), | ||||
|             Genre("искусство"), | ||||
|             Genre("история"), | ||||
|             Genre("киберпанк"), | ||||
|             Genre("кодомо"), | ||||
|             Genre("комедия"), | ||||
|             Genre("литРПГ"), | ||||
|             Genre("магия"), | ||||
|             Genre("махо-сёдзё"), | ||||
|             Genre("меха"), | ||||
|             Genre("мистика"), | ||||
|             Genre("музыка"), | ||||
|             Genre("научная фантастика"), | ||||
|             Genre("повседневность"), | ||||
|             Genre("постапокалиптика"), | ||||
|             Genre("приключения"), | ||||
|             Genre("психология"), | ||||
|             Genre("романтика"), | ||||
|             Genre("самурайский боевик"), | ||||
|             Genre("сборник"), | ||||
|             Genre("сверхъестественное"), | ||||
|             Genre("сказка"), | ||||
|             Genre("спорт"), | ||||
|             Genre("супергерои"), | ||||
|             Genre("сэйнэн"), | ||||
|             Genre("сёдзё"), | ||||
|             Genre("сёдзё-ай"), | ||||
|             Genre("сёнэн"), | ||||
|             Genre("сёнэн-ай"), | ||||
|             Genre("тентакли"), | ||||
|             Genre("трагедия"), | ||||
|             Genre("триллер"), | ||||
|             Genre("ужасы"), | ||||
|             Genre("фантастика"), | ||||
|             Genre("фурри"), | ||||
|             Genre("фэнтези"), | ||||
|             Genre("школа"), | ||||
|             Genre("эротика"), | ||||
|             Genre("юри"), | ||||
|             Genre("яой"), | ||||
|             Genre("ёнкома") | ||||
|     ) | ||||
| } | ||||
| @@ -25,8 +25,8 @@ class Mintmanga(override val id: Int) : ParsedOnlineSource() { | ||||
|  | ||||
|     override fun latestUpdatesInitialUrl() = "$baseUrl/list?sortType=updated" | ||||
|  | ||||
|     override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = | ||||
|             "$baseUrl/search?q=$query&${filters.map { it.id + "=in" }.joinToString("&")}" | ||||
|     override fun searchMangaInitialUrl(query: String, filters: List<Filter<*>>) = | ||||
|             "$baseUrl/search?q=$query&${filters.map { (it as Genre).id + arrayOf("=", "=in", "=ex")[it.state] }.joinToString("&")}" | ||||
|  | ||||
|     override fun popularMangaSelector() = "div.desc" | ||||
|  | ||||
| @@ -107,57 +107,60 @@ class Mintmanga(override val id: Int) : ParsedOnlineSource() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun pageListParse(document: Document, pages: MutableList<Page>) { } | ||||
|     override fun pageListParse(document: Document, pages: MutableList<Page>) { | ||||
|     } | ||||
|  | ||||
|     override fun imageUrlParse(document: Document) = "" | ||||
|  | ||||
|     private class Genre(name: String, val id: String) : Filter.TriState(name) | ||||
|  | ||||
|     /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")].map((el,i) => { | ||||
|     *  const onClick=el.getAttribute('onclick');const id=onClick.substr(31,onClick.length-33); | ||||
|     *  return `Filter("${id}", "${el.textContent.trim()}")` }).join(',\n') | ||||
|     *  return `Genre("${el.textContent.trim()}", "${id}")` }).join(',\n') | ||||
|     *  on http://mintmanga.com/search | ||||
|     */ | ||||
|     override fun getFilterList(): List<Filter> = listOf( | ||||
|             Filter("el_2220", "арт"), | ||||
|             Filter("el_1353", "бара"), | ||||
|             Filter("el_1346", "боевик"), | ||||
|             Filter("el_1334", "боевые искусства"), | ||||
|             Filter("el_1339", "вампиры"), | ||||
|             Filter("el_1333", "гарем"), | ||||
|             Filter("el_1347", "гендерная интрига"), | ||||
|             Filter("el_1337", "героическое фэнтези"), | ||||
|             Filter("el_1343", "детектив"), | ||||
|             Filter("el_1349", "дзёсэй"), | ||||
|             Filter("el_1332", "додзинси"), | ||||
|             Filter("el_1310", "драма"), | ||||
|             Filter("el_5229", "игра"), | ||||
|             Filter("el_1311", "история"), | ||||
|             Filter("el_1351", "киберпанк"), | ||||
|             Filter("el_1328", "комедия"), | ||||
|             Filter("el_1318", "меха"), | ||||
|             Filter("el_1324", "мистика"), | ||||
|             Filter("el_1325", "научная фантастика"), | ||||
|             Filter("el_1327", "повседневность"), | ||||
|             Filter("el_1342", "постапокалиптика"), | ||||
|             Filter("el_1322", "приключения"), | ||||
|             Filter("el_1335", "психология"), | ||||
|             Filter("el_1313", "романтика"), | ||||
|             Filter("el_1316", "самурайский боевик"), | ||||
|             Filter("el_1350", "сверхъестественное"), | ||||
|             Filter("el_1314", "сёдзё"), | ||||
|             Filter("el_1320", "сёдзё-ай"), | ||||
|             Filter("el_1326", "сёнэн"), | ||||
|             Filter("el_1330", "сёнэн-ай"), | ||||
|             Filter("el_1321", "спорт"), | ||||
|             Filter("el_1329", "сэйнэн"), | ||||
|             Filter("el_1344", "трагедия"), | ||||
|             Filter("el_1341", "триллер"), | ||||
|             Filter("el_1317", "ужасы"), | ||||
|             Filter("el_1331", "фантастика"), | ||||
|             Filter("el_1323", "фэнтези"), | ||||
|             Filter("el_1319", "школа"), | ||||
|             Filter("el_1340", "эротика"), | ||||
|             Filter("el_1354", "этти"), | ||||
|             Filter("el_1315", "юри"), | ||||
|             Filter("el_1336", "яой") | ||||
|     override fun getFilterList(): List<Filter<*>> = listOf( | ||||
|             Genre("арт", "el_2220"), | ||||
|             Genre("бара", "el_1353"), | ||||
|             Genre("боевик", "el_1346"), | ||||
|             Genre("боевые искусства", "el_1334"), | ||||
|             Genre("вампиры", "el_1339"), | ||||
|             Genre("гарем", "el_1333"), | ||||
|             Genre("гендерная интрига", "el_1347"), | ||||
|             Genre("героическое фэнтези", "el_1337"), | ||||
|             Genre("детектив", "el_1343"), | ||||
|             Genre("дзёсэй", "el_1349"), | ||||
|             Genre("додзинси", "el_1332"), | ||||
|             Genre("драма", "el_1310"), | ||||
|             Genre("игра", "el_5229"), | ||||
|             Genre("история", "el_1311"), | ||||
|             Genre("киберпанк", "el_1351"), | ||||
|             Genre("комедия", "el_1328"), | ||||
|             Genre("меха", "el_1318"), | ||||
|             Genre("мистика", "el_1324"), | ||||
|             Genre("научная фантастика", "el_1325"), | ||||
|             Genre("повседневность", "el_1327"), | ||||
|             Genre("постапокалиптика", "el_1342"), | ||||
|             Genre("приключения", "el_1322"), | ||||
|             Genre("психология", "el_1335"), | ||||
|             Genre("романтика", "el_1313"), | ||||
|             Genre("самурайский боевик", "el_1316"), | ||||
|             Genre("сверхъестественное", "el_1350"), | ||||
|             Genre("сёдзё", "el_1314"), | ||||
|             Genre("сёдзё-ай", "el_1320"), | ||||
|             Genre("сёнэн", "el_1326"), | ||||
|             Genre("сёнэн-ай", "el_1330"), | ||||
|             Genre("спорт", "el_1321"), | ||||
|             Genre("сэйнэн", "el_1329"), | ||||
|             Genre("трагедия", "el_1344"), | ||||
|             Genre("триллер", "el_1341"), | ||||
|             Genre("ужасы", "el_1317"), | ||||
|             Genre("фантастика", "el_1331"), | ||||
|             Genre("фэнтези", "el_1323"), | ||||
|             Genre("школа", "el_1319"), | ||||
|             Genre("эротика", "el_1340"), | ||||
|             Genre("этти", "el_1354"), | ||||
|             Genre("юри", "el_1315"), | ||||
|             Genre("яой", "el_1336") | ||||
|     ) | ||||
| } | ||||
| @@ -25,8 +25,8 @@ class Readmanga(override val id: Int) : ParsedOnlineSource() { | ||||
|  | ||||
|     override fun latestUpdatesInitialUrl() = "$baseUrl/list?sortType=updated" | ||||
|  | ||||
|     override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = | ||||
|             "$baseUrl/search?q=$query&${filters.map { it.id + "=in" }.joinToString("&")}" | ||||
|     override fun searchMangaInitialUrl(query: String, filters: List<Filter<*>>) = | ||||
|             "$baseUrl/search?q=$query&${filters.map { (it as Genre).id + arrayOf("=", "=in", "=ex")[it.state] }.joinToString("&")}" | ||||
|  | ||||
|     override fun popularMangaSelector() = "div.desc" | ||||
|  | ||||
| @@ -107,56 +107,59 @@ class Readmanga(override val id: Int) : ParsedOnlineSource() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun pageListParse(document: Document, pages: MutableList<Page>) { } | ||||
|     override fun pageListParse(document: Document, pages: MutableList<Page>) { | ||||
|     } | ||||
|  | ||||
|     override fun imageUrlParse(document: Document) = "" | ||||
|  | ||||
|     private class Genre(name: String, val id: String) : Filter.TriState(name) | ||||
|  | ||||
|     /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")].map((el,i) => { | ||||
|     *  const onClick=el.getAttribute('onclick');const id=onClick.substr(31,onClick.length-33); | ||||
|     *  return `Filter("${id}", "${el.textContent.trim()}")` }).join(',\n') | ||||
|     *  return `Genre("${el.textContent.trim()}", "${id}")` }).join(',\n') | ||||
|     *  on http://readmanga.me/search | ||||
|     */ | ||||
|     override fun getFilterList(): List<Filter> = listOf( | ||||
|             Filter("el_5685", "арт"), | ||||
|             Filter("el_2155", "боевик"), | ||||
|             Filter("el_2143", "боевые искусства"), | ||||
|             Filter("el_2148", "вампиры"), | ||||
|             Filter("el_2142", "гарем"), | ||||
|             Filter("el_2156", "гендерная интрига"), | ||||
|             Filter("el_2146", "героическое фэнтези"), | ||||
|             Filter("el_2152", "детектив"), | ||||
|             Filter("el_2158", "дзёсэй"), | ||||
|             Filter("el_2141", "додзинси"), | ||||
|             Filter("el_2118", "драма"), | ||||
|             Filter("el_2154", "игра"), | ||||
|             Filter("el_2119", "история"), | ||||
|             Filter("el_8032", "киберпанк"), | ||||
|             Filter("el_2137", "кодомо"), | ||||
|             Filter("el_2136", "комедия"), | ||||
|             Filter("el_2147", "махо-сёдзё"), | ||||
|             Filter("el_2126", "меха"), | ||||
|             Filter("el_2132", "мистика"), | ||||
|             Filter("el_2133", "научная фантастика"), | ||||
|             Filter("el_2135", "повседневность"), | ||||
|             Filter("el_2151", "постапокалиптика"), | ||||
|             Filter("el_2130", "приключения"), | ||||
|             Filter("el_2144", "психология"), | ||||
|             Filter("el_2121", "романтика"), | ||||
|             Filter("el_2124", "самурайский боевик"), | ||||
|             Filter("el_2159", "сверхъестественное"), | ||||
|             Filter("el_2122", "сёдзё"), | ||||
|             Filter("el_2128", "сёдзё-ай"), | ||||
|             Filter("el_2134", "сёнэн"), | ||||
|             Filter("el_2139", "сёнэн-ай"), | ||||
|             Filter("el_2129", "спорт"), | ||||
|             Filter("el_2138", "сэйнэн"), | ||||
|             Filter("el_2153", "трагедия"), | ||||
|             Filter("el_2150", "триллер"), | ||||
|             Filter("el_2125", "ужасы"), | ||||
|             Filter("el_2140", "фантастика"), | ||||
|             Filter("el_2131", "фэнтези"), | ||||
|             Filter("el_2127", "школа"), | ||||
|             Filter("el_2149", "этти"), | ||||
|             Filter("el_2123", "юри") | ||||
|     override fun getFilterList(): List<Filter<*>> = listOf( | ||||
|             Genre("арт", "el_5685"), | ||||
|             Genre("боевик", "el_2155"), | ||||
|             Genre("боевые искусства", "el_2143"), | ||||
|             Genre("вампиры", "el_2148"), | ||||
|             Genre("гарем", "el_2142"), | ||||
|             Genre("гендерная интрига", "el_2156"), | ||||
|             Genre("героическое фэнтези", "el_2146"), | ||||
|             Genre("детектив", "el_2152"), | ||||
|             Genre("дзёсэй", "el_2158"), | ||||
|             Genre("додзинси", "el_2141"), | ||||
|             Genre("драма", "el_2118"), | ||||
|             Genre("игра", "el_2154"), | ||||
|             Genre("история", "el_2119"), | ||||
|             Genre("киберпанк", "el_8032"), | ||||
|             Genre("кодомо", "el_2137"), | ||||
|             Genre("комедия", "el_2136"), | ||||
|             Genre("махо-сёдзё", "el_2147"), | ||||
|             Genre("меха", "el_2126"), | ||||
|             Genre("мистика", "el_2132"), | ||||
|             Genre("научная фантастика", "el_2133"), | ||||
|             Genre("повседневность", "el_2135"), | ||||
|             Genre("постапокалиптика", "el_2151"), | ||||
|             Genre("приключения", "el_2130"), | ||||
|             Genre("психология", "el_2144"), | ||||
|             Genre("романтика", "el_2121"), | ||||
|             Genre("самурайский боевик", "el_2124"), | ||||
|             Genre("сверхъестественное", "el_2159"), | ||||
|             Genre("сёдзё", "el_2122"), | ||||
|             Genre("сёдзё-ай", "el_2128"), | ||||
|             Genre("сёнэн", "el_2134"), | ||||
|             Genre("сёнэн-ай", "el_2139"), | ||||
|             Genre("спорт", "el_2129"), | ||||
|             Genre("сэйнэн", "el_2138"), | ||||
|             Genre("трагедия", "el_2153"), | ||||
|             Genre("триллер", "el_2150"), | ||||
|             Genre("ужасы", "el_2125"), | ||||
|             Genre("фантастика", "el_2140"), | ||||
|             Genre("фэнтези", "el_2131"), | ||||
|             Genre("школа", "el_2127"), | ||||
|             Genre("этти", "el_2149"), | ||||
|             Genre("юри", "el_2123") | ||||
|     ) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user