Remove MangaCow, MangaFox, MangaGo, MangaPlus (EN), ManwhaHentai (#1235)
Remove MangaCow, MangaFox, MangaGo, MangaPlus (EN), ManwhaHentai
| @ -1,12 +0,0 @@ | |||||||
| apply plugin: 'com.android.application' |  | ||||||
| apply plugin: 'kotlin-android' |  | ||||||
| 
 |  | ||||||
| ext { |  | ||||||
|     appName = 'Tachiyomi: Mangacow' |  | ||||||
|     pkgNameSuffix = 'en.mangacow' |  | ||||||
|     extClass = '.Mangacow' |  | ||||||
|     extVersionCode = 1 |  | ||||||
|     libVersion = '1.0' |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| apply from: "$rootDir/common.gradle" |  | ||||||
| @ -1,165 +0,0 @@ | |||||||
| package eu.kanade.tachiyomi.extension.en.mangacow |  | ||||||
| 
 |  | ||||||
| import eu.kanade.tachiyomi.network.GET |  | ||||||
| import eu.kanade.tachiyomi.network.POST |  | ||||||
| import eu.kanade.tachiyomi.source.model.* |  | ||||||
| import eu.kanade.tachiyomi.source.online.ParsedHttpSource |  | ||||||
| import eu.kanade.tachiyomi.util.asJsoup |  | ||||||
| import okhttp3.FormBody |  | ||||||
| import okhttp3.OkHttpClient |  | ||||||
| import okhttp3.Request |  | ||||||
| import okhttp3.Response |  | ||||||
| import org.jsoup.nodes.Document |  | ||||||
| import org.jsoup.nodes.Element |  | ||||||
| import java.text.ParseException |  | ||||||
| import java.text.SimpleDateFormat |  | ||||||
| import java.util.Calendar |  | ||||||
| import java.util.regex.Pattern |  | ||||||
| 
 |  | ||||||
| class Mangacow : ParsedHttpSource() { |  | ||||||
|     override val name = "Mangacow" |  | ||||||
| 
 |  | ||||||
|     override val baseUrl = "http://mngcow.co" |  | ||||||
| 
 |  | ||||||
|     override val lang = "en" |  | ||||||
| 
 |  | ||||||
|     override val supportsLatest = true |  | ||||||
| 
 |  | ||||||
|     override val client: OkHttpClient = network.cloudflareClient |  | ||||||
| 
 |  | ||||||
|     companion object { |  | ||||||
|         val pagesUrlPattern by lazy { |  | ||||||
|             Pattern.compile("""arr_img.push\(\"(.*?)\"\)""") |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         val dateFormat by lazy { |  | ||||||
|             SimpleDateFormat("MMM dd, yyyy") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun popularMangaSelector() = "ul#wpm_mng_lst > li > div.det > a" |  | ||||||
| 
 |  | ||||||
|     override fun latestUpdatesSelector() = popularMangaSelector() |  | ||||||
| 
 |  | ||||||
|     override fun popularMangaRequest(page: Int) |  | ||||||
|         = GET("$baseUrl/manga-list/all/any/most-popular/$page/", headers) |  | ||||||
| 
 |  | ||||||
|     override fun latestUpdatesRequest(page: Int) |  | ||||||
|         = GET("$baseUrl/manga-list/all/any/last-updated/$page/", headers) |  | ||||||
| 
 |  | ||||||
|     override fun popularMangaFromElement(element: Element): SManga { |  | ||||||
|         val manga = SManga.create() |  | ||||||
|         manga.setUrlWithoutDomain(element.attr("href")) |  | ||||||
|         manga.title = element.text().trim() |  | ||||||
|         return manga |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun latestUpdatesFromElement(element: Element): SManga { |  | ||||||
|         return popularMangaFromElement(element) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Mangacow does not have many series, so everything is displayed on a single page. |  | ||||||
|     // If this changes someday, I will update *NextPageSelector() accordingly. |  | ||||||
|     override fun popularMangaNextPageSelector() = null |  | ||||||
| 
 |  | ||||||
|     override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() |  | ||||||
| 
 |  | ||||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { |  | ||||||
|         val form = FormBody.Builder().apply { |  | ||||||
|             add("txt_wpm_wgt_mng_sch_nme", query) |  | ||||||
|             add("cmd_wpm_wgt_mng_sch_sbm", "1") |  | ||||||
|         } |  | ||||||
|         return POST("${baseUrl}/manga-list/search/", headers, form.build()) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun searchMangaSelector() = popularMangaSelector() |  | ||||||
| 
 |  | ||||||
|     override fun searchMangaFromElement(element: Element): SManga { |  | ||||||
|         return popularMangaFromElement(element) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() |  | ||||||
| 
 |  | ||||||
|     override fun mangaDetailsParse(document: Document) = SManga.create().apply { |  | ||||||
|         val infoElement = document.select("div.mng_ifo").first() |  | ||||||
| 
 |  | ||||||
|         author = infoElement.select("a[href*='/manga-list/author/']").map { |  | ||||||
|             it.text().trim() |  | ||||||
|         }.joinToString(", ") |  | ||||||
|         artist = infoElement.select("a[href*='/manga-list/author/']").map { |  | ||||||
|             it.text().trim() |  | ||||||
|         }.joinToString(", ") |  | ||||||
|         genre = infoElement.select("a[href*='/manga-list/category/']").map { |  | ||||||
|             it.text().trim() |  | ||||||
|         }.joinToString(", ") |  | ||||||
|         description = infoElement.select("div.mngdesc").first()?.text()?.trim() |  | ||||||
|         status = infoElement.select("a[href*='/manga-list/status/']").first().text().let { |  | ||||||
|             parseStatus(it) |  | ||||||
|         } |  | ||||||
|         thumbnail_url = infoElement.select("div.cvr_ara > img.cvr")?.attr("src") |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private fun parseStatus(status: String) = when { |  | ||||||
|         status.contains("Ongoing") -> SManga.ONGOING |  | ||||||
|         status.contains("Completed") -> SManga.COMPLETED |  | ||||||
|         else -> SManga.UNKNOWN |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun chapterListParse(response: Response): List<SChapter> { |  | ||||||
|         var response = response |  | ||||||
|         val chapters = mutableListOf<SChapter>() |  | ||||||
|         do { |  | ||||||
|             val document = response.asJsoup() |  | ||||||
|             document.select(chapterListSelector()).forEach { |  | ||||||
|                 chapters.add(chapterFromElement(it)) |  | ||||||
|             } |  | ||||||
|             val nextPage = chapterListNextPageSelector().let { document.select(it).first() } |  | ||||||
|             if (nextPage != null) { |  | ||||||
|                 response = client.newCall(GET(nextPage.attr("href"))).execute() |  | ||||||
|             } |  | ||||||
|         } while (nextPage != null) |  | ||||||
|         return chapters |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private fun chapterListNextPageSelector() = "ul.pgg > li > a:contains(Next)" |  | ||||||
| 
 |  | ||||||
|     override fun chapterListSelector() = "ul.mng_chp > li.lng_ > a.lst" |  | ||||||
| 
 |  | ||||||
|     override fun chapterFromElement(element: Element) = SChapter.create().apply { |  | ||||||
|         setUrlWithoutDomain(element.attr("href")) |  | ||||||
|         name = element.select("b.val").first().text().trim() |  | ||||||
|         date_upload = element.select("b.dte").text()?.substringAfterLast("Published on ")?.trim()?.let { |  | ||||||
|             parseChapterDate(it) |  | ||||||
|         } ?: 0L |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private fun parseChapterDate(date: String): Long { |  | ||||||
|        return try { |  | ||||||
|             dateFormat.parse(date).time |  | ||||||
|         } catch (e: ParseException) { |  | ||||||
|             0L |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun pageListRequest(chapter: SChapter) = POST(baseUrl + chapter.url, headers) |  | ||||||
| 
 |  | ||||||
|     override fun pageListParse(response: Response): List<Page> { |  | ||||||
|         val pages = mutableListOf<Page>() |  | ||||||
|         val m = pagesUrlPattern.matcher(response.body()!!.string()) |  | ||||||
|         var i = 0 |  | ||||||
|         while (m.find()) { |  | ||||||
|             pages.add(Page(i++, "", m.group(1))) |  | ||||||
|         } |  | ||||||
|         return pages |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun pageListParse(document: Document): List<Page> { |  | ||||||
|         throw Exception("Not used") |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun imageUrlRequest(page: Page) = GET(page.url) |  | ||||||
| 
 |  | ||||||
|     override fun imageUrlParse(document: Document) = "" |  | ||||||
| 
 |  | ||||||
|     override fun getFilterList() = FilterList() |  | ||||||
| } |  | ||||||
| @ -1,12 +0,0 @@ | |||||||
| apply plugin: 'com.android.application' |  | ||||||
| apply plugin: 'kotlin-android' |  | ||||||
| 
 |  | ||||||
| ext { |  | ||||||
|     appName = 'Tachiyomi: Mangafox' |  | ||||||
|     pkgNameSuffix = 'en.mangafox' |  | ||||||
|     extClass = '.Mangafox' |  | ||||||
|     extVersionCode = 3 |  | ||||||
|     libVersion = '1.2' |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| apply from: "$rootDir/common.gradle" |  | ||||||
| Before Width: | Height: | Size: 5.6 KiB | 
| Before Width: | Height: | Size: 3.0 KiB | 
| Before Width: | Height: | Size: 8.3 KiB | 
| Before Width: | Height: | Size: 15 KiB | 
| Before Width: | Height: | Size: 23 KiB | 
| Before Width: | Height: | Size: 100 KiB | 
| @ -1,230 +0,0 @@ | |||||||
| package eu.kanade.tachiyomi.extension.en.mangafox |  | ||||||
| import eu.kanade.tachiyomi.network.GET |  | ||||||
| import eu.kanade.tachiyomi.source.model.* |  | ||||||
| import eu.kanade.tachiyomi.source.online.ParsedHttpSource |  | ||||||
| import okhttp3.HttpUrl |  | ||||||
| import okhttp3.Request |  | ||||||
| import org.jsoup.nodes.Document |  | ||||||
| import org.jsoup.nodes.Element |  | ||||||
| import java.text.ParseException |  | ||||||
| import java.text.SimpleDateFormat |  | ||||||
| import java.util.* |  | ||||||
| 
 |  | ||||||
| class Mangafox : ParsedHttpSource() { |  | ||||||
| 
 |  | ||||||
|     override val id: Long = 3 |  | ||||||
| 
 |  | ||||||
|     override val name = "Mangafox" |  | ||||||
| 
 |  | ||||||
|     override val baseUrl = "http://fanfox.net" |  | ||||||
| 
 |  | ||||||
|     override val lang = "en" |  | ||||||
| 
 |  | ||||||
|     override val supportsLatest = true |  | ||||||
| 
 |  | ||||||
|     override fun popularMangaSelector() = "div#mangalist > ul.list > li" |  | ||||||
| 
 |  | ||||||
|     override fun popularMangaRequest(page: Int): Request { |  | ||||||
|         val pageStr = if (page != 1) "$page.htm" else "" |  | ||||||
|         return GET("$baseUrl/directory/$pageStr", headers) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun latestUpdatesSelector() = "div#mangalist > ul.list > li" |  | ||||||
| 
 |  | ||||||
|     override fun latestUpdatesRequest(page: Int): Request { |  | ||||||
|         val pageStr = if (page != 1) "$page.htm" else "" |  | ||||||
|         return GET("$baseUrl/directory/$pageStr?latest") |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun popularMangaFromElement(element: Element): SManga { |  | ||||||
|         val manga = SManga.create() |  | ||||||
|         element.select("a.title").first().let { |  | ||||||
|             manga.setUrlWithoutDomain(it.attr("href")) |  | ||||||
|             manga.title = it.text() |  | ||||||
|         } |  | ||||||
|         return manga |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun latestUpdatesFromElement(element: Element): SManga { |  | ||||||
|         return popularMangaFromElement(element) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun popularMangaNextPageSelector() = "a:has(span.next)" |  | ||||||
| 
 |  | ||||||
|     override fun latestUpdatesNextPageSelector() = "a:has(span.next)" |  | ||||||
| 
 |  | ||||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { |  | ||||||
|         val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1")!!.newBuilder().addQueryParameter("name", query) |  | ||||||
|         (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> |  | ||||||
|             when (filter) { |  | ||||||
|                 is Status -> url.addQueryParameter(filter.id, filter.state.toString()) |  | ||||||
|                 is GenreList -> filter.state.forEach { genre -> url.addQueryParameter(genre.id, genre.state.toString()) } |  | ||||||
|                 is TextField -> url.addQueryParameter(filter.key, filter.state) |  | ||||||
|                 is Type -> url.addQueryParameter("type", if (filter.state == 0) "" else filter.state.toString()) |  | ||||||
|                 is OrderBy -> { |  | ||||||
|                     url.addQueryParameter("sort", arrayOf("name", "rating", "views", "total_chapters", "last_chapter_time")[filter.state!!.index]) |  | ||||||
|                     url.addQueryParameter("order", if (filter.state?.ascending == true) "az" else "za") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         url.addQueryParameter("page", page.toString()) |  | ||||||
|         return GET(url.toString(), headers) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun searchMangaSelector() = "div#mangalist > ul.list > li" |  | ||||||
| 
 |  | ||||||
|     override fun searchMangaFromElement(element: Element): SManga { |  | ||||||
|         val manga = SManga.create() |  | ||||||
|         element.select("a.title").first().let { |  | ||||||
|             manga.setUrlWithoutDomain(it.attr("href")) |  | ||||||
|             manga.title = it.text() |  | ||||||
|         } |  | ||||||
|         return manga |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun searchMangaNextPageSelector() = "a:has(span.next)" |  | ||||||
| 
 |  | ||||||
|     override fun mangaDetailsParse(document: Document): SManga { |  | ||||||
|         val infoElement = document.select("div#title").first() |  | ||||||
|         val rowElement = infoElement.select("table > tbody > tr:eq(1)").first() |  | ||||||
|         val sideInfoElement = document.select("#series_info").first() |  | ||||||
|         val licensedElement = document.select("div.warning").first() |  | ||||||
| 
 |  | ||||||
|         val manga = SManga.create() |  | ||||||
|         manga.author = rowElement.select("td:eq(1)").first()?.text() |  | ||||||
|         manga.artist = rowElement.select("td:eq(2)").first()?.text() |  | ||||||
|         manga.genre = rowElement.select("td:eq(3)").first()?.text() |  | ||||||
|         manga.description = infoElement.select("p.summary").first()?.text() |  | ||||||
|         val isLicensed = licensedElement?.text()?.contains("licensed") |  | ||||||
|         if (isLicensed == true) { |  | ||||||
|             manga.status = SManga.LICENSED |  | ||||||
|         } else { |  | ||||||
|             manga.status = sideInfoElement.select(".data").first()?.text().orEmpty().let { parseStatus(it) } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         manga.thumbnail_url = sideInfoElement.select("div.cover > img").first()?.attr("src") |  | ||||||
|         return manga |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private fun parseStatus(status: String) = when { |  | ||||||
|         status.contains("Ongoing") -> SManga.ONGOING |  | ||||||
|         status.contains("Completed") -> SManga.COMPLETED |  | ||||||
|         else -> SManga.UNKNOWN |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun chapterListSelector() = "div#chapters li div" |  | ||||||
| 
 |  | ||||||
|     override fun chapterFromElement(element: Element): SChapter { |  | ||||||
|         val urlElement = element.select("a.tips").first() |  | ||||||
| 
 |  | ||||||
|         val chapter = SChapter.create() |  | ||||||
|         chapter.setUrlWithoutDomain(urlElement.attr("href")) |  | ||||||
|         chapter.name = element.select("span.title.nowrap").first()?.text()?.let { urlElement.text() + " - " + it } ?: urlElement.text() |  | ||||||
|         chapter.date_upload = element.select("span.date").first()?.text()?.let { parseChapterDate(it) } ?: 0 |  | ||||||
|         return chapter |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private fun parseChapterDate(date: String): Long { |  | ||||||
|         return if ("Today" in date || " ago" in date) { |  | ||||||
|             Calendar.getInstance().apply { |  | ||||||
|                 set(Calendar.HOUR_OF_DAY, 0) |  | ||||||
|                 set(Calendar.MINUTE, 0) |  | ||||||
|                 set(Calendar.SECOND, 0) |  | ||||||
|                 set(Calendar.MILLISECOND, 0) |  | ||||||
|             }.timeInMillis |  | ||||||
|         } else if ("Yesterday" in date) { |  | ||||||
|             Calendar.getInstance().apply { |  | ||||||
|                 add(Calendar.DATE, -1) |  | ||||||
|                 set(Calendar.HOUR_OF_DAY, 0) |  | ||||||
|                 set(Calendar.MINUTE, 0) |  | ||||||
|                 set(Calendar.SECOND, 0) |  | ||||||
|                 set(Calendar.MILLISECOND, 0) |  | ||||||
|             }.timeInMillis |  | ||||||
|         } else { |  | ||||||
|             try { |  | ||||||
|                 SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH).parse(date).time |  | ||||||
|             } catch (e: ParseException) { |  | ||||||
|                 0L |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun pageListParse(document: Document): List<Page> { |  | ||||||
|         val url = document.baseUri().substringBeforeLast('/') |  | ||||||
| 
 |  | ||||||
|         val pages = mutableListOf<Page>() |  | ||||||
|         document.select("select.m").first()?.select("option:not([value=0])")?.forEach { |  | ||||||
|             pages.add(Page(pages.size, "$url/${it.attr("value")}.html")) |  | ||||||
|         } |  | ||||||
|         return pages |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun imageUrlParse(document: Document): String { |  | ||||||
|         val url = document.getElementById("image").attr("src") |  | ||||||
|         return if ("compressed?token=" !in url) { |  | ||||||
|             url |  | ||||||
|         } else { |  | ||||||
|             "http://mangafox.me/media/logo.png" |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private class Status(val id: String = "is_completed") : 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 Type : Filter.Select<String>("Type", arrayOf("Any", "Japanese Manga", "Korean Manhwa", "Chinese Manhua")) |  | ||||||
|     private class OrderBy : Filter.Sort("Order by", |  | ||||||
|             arrayOf("Series name", "Rating", "Views", "Total chapters", "Last chapter"), |  | ||||||
|             Filter.Sort.Selection(2, false)) |  | ||||||
| 
 |  | ||||||
|     private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) |  | ||||||
| 
 |  | ||||||
|     override fun getFilterList() = FilterList( |  | ||||||
|             TextField("Author", "author"), |  | ||||||
|             TextField("Artist", "artist"), |  | ||||||
|             Type(), |  | ||||||
|             Status(), |  | ||||||
|             OrderBy(), |  | ||||||
|             GenreList(getGenreList()) |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     // $('select.genres').map((i,el)=>`Genre("${$(el).next().text().trim()}", "${$(el).attr('name')}")`).get().join(',\n') |  | ||||||
|     // on http://mangafox.me/search.php |  | ||||||
|     private fun getGenreList() = listOf( |  | ||||||
|             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") |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| @ -1,16 +0,0 @@ | |||||||
| apply plugin: 'com.android.application' |  | ||||||
| apply plugin: 'kotlin-android' |  | ||||||
| 
 |  | ||||||
| ext { |  | ||||||
|     appName = 'Tachiyomi: Mangago' |  | ||||||
|     pkgNameSuffix = 'en.mangago' |  | ||||||
|     extClass = '.Mangago' |  | ||||||
|     extVersionCode = 7 |  | ||||||
|     libVersion = '1.2' |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| dependencies { |  | ||||||
|     compileOnly project(':duktape-stub') |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| apply from: "$rootDir/common.gradle" |  | ||||||
| Before Width: | Height: | Size: 4.9 KiB | 
| Before Width: | Height: | Size: 2.9 KiB | 
| Before Width: | Height: | Size: 7.3 KiB | 
| Before Width: | Height: | Size: 14 KiB | 
| Before Width: | Height: | Size: 23 KiB | 
| Before Width: | Height: | Size: 114 KiB | 
| @ -1,429 +0,0 @@ | |||||||
| package eu.kanade.tachiyomi.extension.en.mangago |  | ||||||
| 
 |  | ||||||
| import android.graphics.Bitmap |  | ||||||
| import android.graphics.BitmapFactory |  | ||||||
| import android.graphics.Canvas |  | ||||||
| import android.graphics.Rect |  | ||||||
| import android.net.Uri |  | ||||||
| import com.squareup.duktape.Duktape |  | ||||||
| import eu.kanade.tachiyomi.network.GET |  | ||||||
| import eu.kanade.tachiyomi.source.model.* |  | ||||||
| import eu.kanade.tachiyomi.source.online.ParsedHttpSource |  | ||||||
| import okhttp3.MediaType |  | ||||||
| import okhttp3.Request |  | ||||||
| import okhttp3.ResponseBody |  | ||||||
| import org.jsoup.nodes.Document |  | ||||||
| import org.jsoup.nodes.Element |  | ||||||
| import java.io.ByteArrayOutputStream |  | ||||||
| import java.io.InputStream |  | ||||||
| import java.text.SimpleDateFormat |  | ||||||
| import java.util.* |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Mangago source |  | ||||||
|  */ |  | ||||||
| 
 |  | ||||||
| class Mangago : ParsedHttpSource() { |  | ||||||
|     override val lang = "en" |  | ||||||
|     override val supportsLatest = true |  | ||||||
|     override val name = "Mangago" |  | ||||||
|     override val baseUrl = "https://www.mangago.me" |  | ||||||
| 
 |  | ||||||
|     override val client = network.cloudflareClient.newBuilder().addInterceptor { chain -> |  | ||||||
|         val request = chain.request() |  | ||||||
|         val response = chain.proceed(request) |  | ||||||
|         if (!request.url().pathSegments().contains("cspiclink")) return@addInterceptor response |  | ||||||
| 
 |  | ||||||
|         val res = response.body()!!.byteStream().use { |  | ||||||
|             decodeImage(request.url().toString(), it) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         val rb = ResponseBody.create(MediaType.parse("image/png"), res) |  | ||||||
|         response.newBuilder().body(rb).build() |  | ||||||
|     }.build() |  | ||||||
| 
 |  | ||||||
|     //Hybrid selector that selects manga from either the genre listing or the search results |  | ||||||
|     private val genreListingSelector = ".updatesli" |  | ||||||
|     private val genreListingNextPageSelector = ".current+li > a" |  | ||||||
| 
 |  | ||||||
|     private val dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH) |  | ||||||
| 
 |  | ||||||
|     override fun popularMangaSelector() = genreListingSelector |  | ||||||
| 
 |  | ||||||
|     private fun mangaFromElement(element: Element) = SManga.create().apply { |  | ||||||
|         val linkElement = element.select(".thm-effect") |  | ||||||
| 
 |  | ||||||
|         setUrlWithoutDomain(linkElement.attr("href")) |  | ||||||
| 
 |  | ||||||
|         title = linkElement.attr("title") |  | ||||||
| 
 |  | ||||||
|         thumbnail_url = linkElement.first().child(0).attr("src") |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun popularMangaFromElement(element: Element) = mangaFromElement(element) |  | ||||||
| 
 |  | ||||||
|     override fun popularMangaNextPageSelector() = genreListingNextPageSelector |  | ||||||
| 
 |  | ||||||
|     //Hybrid selector that selects manga from either the genre listing or the search results |  | ||||||
|     override fun searchMangaSelector() = "$genreListingSelector, .pic_list .box" |  | ||||||
| 
 |  | ||||||
|     override fun searchMangaFromElement(element: Element) = mangaFromElement(element) |  | ||||||
| 
 |  | ||||||
|     override fun searchMangaNextPageSelector() = genreListingNextPageSelector |  | ||||||
| 
 |  | ||||||
|     override fun popularMangaRequest(page: Int) = GET("$baseUrl/genre/all/$page/?f=1&o=1&sortby=view&e=") |  | ||||||
| 
 |  | ||||||
|     override fun latestUpdatesSelector() = genreListingSelector |  | ||||||
| 
 |  | ||||||
|     override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element) |  | ||||||
| 
 |  | ||||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { |  | ||||||
|         //If text search is active use text search, otherwise use genre search |  | ||||||
|         val url = if (query.isNotBlank()) { |  | ||||||
|             Uri.parse("$baseUrl/r/l_search/") |  | ||||||
|                     .buildUpon() |  | ||||||
|                     .appendQueryParameter("name", query) |  | ||||||
|                     .appendQueryParameter("page", page.toString()) |  | ||||||
|                     .toString() |  | ||||||
|         } else { |  | ||||||
|             val uri = Uri.parse("$baseUrl/genre/").buildUpon() |  | ||||||
|             val genres = filters.flatMap { |  | ||||||
|                 (it as? GenreGroup)?.stateList ?: emptyList() |  | ||||||
|             } |  | ||||||
|             //Append included genres |  | ||||||
|             val activeGenres = genres.filter { it.isIncluded() } |  | ||||||
|             uri.appendPath(if (activeGenres.isEmpty()) |  | ||||||
|                 "all" |  | ||||||
|             else |  | ||||||
|                 activeGenres.joinToString(",", transform = { it.name })) |  | ||||||
|             //Append page number |  | ||||||
|             uri.appendPath(page.toString()) |  | ||||||
|             //Append excluded genres |  | ||||||
|             uri.appendQueryParameter("e", |  | ||||||
|                     genres.filter { it.isExcluded() } |  | ||||||
|                             .joinToString(",", transform = GenreFilter::name)) |  | ||||||
|             //Append uri filters |  | ||||||
|             filters.forEach { |  | ||||||
|                 if (it is UriFilter) |  | ||||||
|                     it.addToUri(uri) |  | ||||||
|             } |  | ||||||
|             uri.toString() |  | ||||||
|         } |  | ||||||
|         return GET(url) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun latestUpdatesNextPageSelector() = genreListingNextPageSelector |  | ||||||
| 
 |  | ||||||
|     override fun mangaDetailsParse(document: Document) = SManga.create().apply { |  | ||||||
|         val coverElement = document.select(".left.cover > img") |  | ||||||
| 
 |  | ||||||
|         title = coverElement.attr("alt") |  | ||||||
| 
 |  | ||||||
|         thumbnail_url = coverElement.attr("src") |  | ||||||
| 
 |  | ||||||
|         document.select(".manga_right td").forEach { |  | ||||||
|             when (it.getElementsByTag("label").text().trim().toLowerCase()) { |  | ||||||
|                 "status:" -> { |  | ||||||
|                     status = when (it.getElementsByTag("span").first().text().trim().toLowerCase()) { |  | ||||||
|                         "ongoing" -> SManga.ONGOING |  | ||||||
|                         "completed" -> SManga.COMPLETED |  | ||||||
|                         else -> SManga.UNKNOWN |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 "author:" -> { |  | ||||||
|                     author = it.getElementsByTag("a").first().text() |  | ||||||
|                 } |  | ||||||
|                 "genre(s):" -> { |  | ||||||
|                     genre = it.getElementsByTag("a").joinToString(transform = { it.text() }) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         description = document.getElementsByClass("manga_summary").first().ownText().trim() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/genre/all/$page/?f=1&o=1&sortby=update_date&e=") |  | ||||||
| 
 |  | ||||||
|     override fun chapterListSelector() = "#chapter_table > tbody > tr" |  | ||||||
| 
 |  | ||||||
|     override fun chapterFromElement(element: Element) = SChapter.create().apply { |  | ||||||
|         val link = element.getElementsByTag("a") |  | ||||||
| 
 |  | ||||||
|         setUrlWithoutDomain(link.attr("href")) |  | ||||||
| 
 |  | ||||||
|         name = link.text().trim() |  | ||||||
| 
 |  | ||||||
|         date_upload = dateFormat.parse(element.getElementsByClass("no").text().trim()).time |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fun decodeImage(url: String, |  | ||||||
|                     img: InputStream): ByteArray { |  | ||||||
|         val decoded = BitmapFactory.decodeStream(img) |  | ||||||
| 
 |  | ||||||
|         val js = "var img = '$url';" + |  | ||||||
|                 "var width = ${decoded.width};" + |  | ||||||
|                 "var height = ${decoded.height};" + |  | ||||||
|                 IMAGE_DESCRAMBLER_JS |  | ||||||
|         val strRes = Duktape.create().use { |  | ||||||
|             it.evaluate(js) as String |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         val result = Bitmap.createBitmap(decoded.width, |  | ||||||
|                 decoded.height, |  | ||||||
|                 Bitmap.Config.ARGB_8888) |  | ||||||
|         val canvas = Canvas(result) |  | ||||||
| 
 |  | ||||||
|         val arrayRes = strRes.split(" ").filter(String::isNotBlank).map { |  | ||||||
|             it.split(",").filter(String::isNotBlank).map(String::toInt) |  | ||||||
|         } |  | ||||||
|         arrayRes.forEach { (srcX, srcY, chunkWidth, chunkHeight, destX, destY) -> |  | ||||||
|             canvas.drawBitmap(decoded, |  | ||||||
|                     Rect(srcX, srcY, srcX + chunkWidth, srcY + chunkHeight), |  | ||||||
|                     Rect(destX, destY, destX + chunkWidth, destY + chunkHeight), |  | ||||||
|                     null) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         val output = ByteArrayOutputStream() |  | ||||||
|         result.compress(Bitmap.CompressFormat.PNG, 100, output) |  | ||||||
|         return output.toByteArray() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private val JS_BEGIN_MARKER = "var imgsrcs = '" |  | ||||||
|     private val JS_END_MARKER = "';" |  | ||||||
|     override fun pageListParse(document: Document): List<Page> { |  | ||||||
|         val imgSrc = document.getElementsByTag("script").map { |  | ||||||
|             it.data().trim() |  | ||||||
|         }.find { |  | ||||||
|             it.startsWith(JS_BEGIN_MARKER) && it.endsWith(JS_END_MARKER) |  | ||||||
|         } ?: throw IllegalArgumentException("Cannot decode imgsrcs!") |  | ||||||
| 
 |  | ||||||
|         val res = Duktape.create().use { |  | ||||||
|             it.evaluate(getCryptoJSLib()) |  | ||||||
|             it.evaluate(getCryptoJSZeroPaddingLib()) |  | ||||||
| 
 |  | ||||||
|             it.evaluate(imgSrc + IMGSRCS_DECODE_JS) as String |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return res.split(",").mapIndexed { i, s -> |  | ||||||
|             Page(i, s, s) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Unused method called!") |  | ||||||
| 
 |  | ||||||
|     override fun getFilterList() = FilterList( |  | ||||||
|             //Mangago does not support genre filtering and text search at the same time |  | ||||||
|             Filter.Header("NOTE: Ignored if using text search!"), |  | ||||||
|             Filter.Separator(), |  | ||||||
|             Filter.Header("Status"), |  | ||||||
|             StatusFilter("Completed", "f"), |  | ||||||
|             StatusFilter("Ongoing", "o"), |  | ||||||
|             GenreGroup(), |  | ||||||
|             SortFilter() |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     // Array.from(document.querySelectorAll('#genre_panel ul li:not(.genres_title) a')).map(a => `GenreFilter("${a.getAttribute('_id')}")`).sort().join(',\n') |  | ||||||
|     // on http://www.mangago.me/genre/all/ |  | ||||||
|     private class GenreGroup : UriFilterGroup<GenreFilter>("Genres", listOf( |  | ||||||
|             GenreFilter("Yaoi"), |  | ||||||
|             GenreFilter("Doujinshi"), |  | ||||||
|             GenreFilter("Shounen Ai"), |  | ||||||
|             GenreFilter("Shoujo"), |  | ||||||
|             GenreFilter("Yuri"), |  | ||||||
|             GenreFilter("Romance"), |  | ||||||
|             GenreFilter("Fantasy"), |  | ||||||
|             GenreFilter("Comedy"), |  | ||||||
|             GenreFilter("Smut"), |  | ||||||
|             GenreFilter("Adult"), |  | ||||||
|             GenreFilter("School Life"), |  | ||||||
|             GenreFilter("Mystery"), |  | ||||||
|             GenreFilter("One Shot"), |  | ||||||
|             GenreFilter("Ecchi"), |  | ||||||
|             GenreFilter("Shounen"), |  | ||||||
|             GenreFilter("Martial Arts"), |  | ||||||
|             GenreFilter("Shoujo Ai"), |  | ||||||
|             GenreFilter("Supernatural"), |  | ||||||
|             GenreFilter("Drama"), |  | ||||||
|             GenreFilter("Action"), |  | ||||||
|             GenreFilter("Adventure"), |  | ||||||
|             GenreFilter("Harem"), |  | ||||||
|             GenreFilter("Historical"), |  | ||||||
|             GenreFilter("Horror"), |  | ||||||
|             GenreFilter("Josei"), |  | ||||||
|             GenreFilter("Mature"), |  | ||||||
|             GenreFilter("Mecha"), |  | ||||||
|             GenreFilter("Psychological"), |  | ||||||
|             GenreFilter("Sci-fi"), |  | ||||||
|             GenreFilter("Seinen"), |  | ||||||
|             GenreFilter("Slice Of Life"), |  | ||||||
|             GenreFilter("Sports"), |  | ||||||
|             GenreFilter("Gender Bender"), |  | ||||||
|             GenreFilter("Tragedy"), |  | ||||||
|             GenreFilter("Bara"), |  | ||||||
|             GenreFilter("Shotacon"), |  | ||||||
|             GenreFilter("Webtoons") |  | ||||||
| 
 |  | ||||||
|     )) |  | ||||||
| 
 |  | ||||||
|     private class GenreFilter(name: String) : Filter.TriState(name) |  | ||||||
| 
 |  | ||||||
|     private class StatusFilter(name: String, val uriParam: String) : Filter.CheckBox(name, true), UriFilter { |  | ||||||
|         override fun addToUri(uri: Uri.Builder) { |  | ||||||
|             uri.appendQueryParameter(uriParam, if (state) "1" else "0") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private class SortFilter : UriSelectFilter("Sort", "sortby", arrayOf( |  | ||||||
|             Pair("random", "Random"), |  | ||||||
|             Pair("view", "Views"), |  | ||||||
|             Pair("comment_count", "Comment Count"), |  | ||||||
|             Pair("create_date", "Creation Date"), |  | ||||||
|             Pair("update_date", "Update Date") |  | ||||||
|     )) |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Class that creates a select filter. Each entry in the dropdown has a name and a display name. |  | ||||||
|      * If an entry is selected it is appended as a query parameter onto the end of the URI. |  | ||||||
|      * If `firstIsUnspecified` is set to true, if the first entry is selected, nothing will be appended on the the URI. |  | ||||||
|      */ |  | ||||||
|     //vals: <name, display> |  | ||||||
|     private open class UriSelectFilter(displayName: String, val uriParam: String, val vals: Array<Pair<String, String>>, |  | ||||||
|                                        val firstIsUnspecified: Boolean = true, |  | ||||||
|                                        defaultValue: Int = 0) : |  | ||||||
|             Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray(), defaultValue), UriFilter { |  | ||||||
|         override fun addToUri(uri: Uri.Builder) { |  | ||||||
|             if (state != 0 || !firstIsUnspecified) |  | ||||||
|                 uri.appendQueryParameter(uriParam, vals[state].first) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Uri filter group |  | ||||||
|      */ |  | ||||||
|     private open class UriFilterGroup<V>(name: String, val stateList: List<V>) : Filter.Group<V>(name, stateList), UriFilter { |  | ||||||
|         override fun addToUri(uri: Uri.Builder) { |  | ||||||
|             stateList.forEach { |  | ||||||
|                 if (it is UriFilter) |  | ||||||
|                     it.addToUri(uri) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Represents a filter that is able to modify a URI. |  | ||||||
|      */ |  | ||||||
|     private interface UriFilter { |  | ||||||
|         fun addToUri(uri: Uri.Builder) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private var libCryptoJS: String? = null |  | ||||||
|     private fun getCryptoJSLib(): String { |  | ||||||
|         if (libCryptoJS == null) { |  | ||||||
|             libCryptoJS = client.newCall(GET("https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js", headers)).execute().body()!!.string() |  | ||||||
|         } |  | ||||||
|         return checkNotNull(libCryptoJS) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private var libCryptoJSZeroPadding: String? = null |  | ||||||
|     private fun getCryptoJSZeroPaddingLib(): String { |  | ||||||
|         if (libCryptoJSZeroPadding == null) { |  | ||||||
|             libCryptoJSZeroPadding = client.newCall(GET("https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/components/pad-zeropadding-min.js", headers)).execute().body()!!.string() |  | ||||||
|         } |  | ||||||
|         return checkNotNull(libCryptoJSZeroPadding) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     companion object { |  | ||||||
|         // https://codepen.io/Th3-822/pen/BVVVGW/ |  | ||||||
|         private const val IMGSRCS_DECODE_JS = """ |  | ||||||
|             function replacePos(a, b, c) { |  | ||||||
|                 return (a.substr(0, b) + c) + a.substring((b + 1), a.length); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             function dorder(a, b) { |  | ||||||
|                 for (j = (b.length - 1); j >= 0; j--) { |  | ||||||
|                     for (i = (a.length - 1); (i - b[j]) >= 0; i--) { |  | ||||||
|                         if ((i % 2) != 0) { |  | ||||||
|                             temp = a[i - b[j]]; |  | ||||||
|                             a = replacePos(a, (i - b[j]), a[i]); |  | ||||||
|                             a = replacePos(a, i, temp); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 return a; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             function decrypt(a, b, c) { |  | ||||||
|                 return CryptoJS.AES.decrypt(a, b, {'iv': c, 'padding': CryptoJS.pad.ZeroPadding}).toString(CryptoJS.enc.Utf8); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             (function() { |  | ||||||
|                 var aesKey = CryptoJS.enc.Hex.parse('e10adc3949ba59abbe56e057f20f883e'); |  | ||||||
|                 var aesIV = CryptoJS.enc.Hex.parse('1234567890abcdef1234567890abcdef'); |  | ||||||
|                 var decrypted = decrypt(imgsrcs, aesKey, aesIV); |  | ||||||
| 
 |  | ||||||
|                 var code = decrypted.charAt(19) + decrypted.charAt(23) + decrypted.charAt(31) + decrypted.charAt(39); |  | ||||||
|                 decrypted = decrypted.slice(0, 19) + decrypted.slice(20, 23) + decrypted.slice(24, 31) + decrypted.slice(32, 39) + decrypted.slice(40); |  | ||||||
| 
 |  | ||||||
|                 return dorder(decrypted, code); |  | ||||||
|                 })(); |  | ||||||
|             """ |  | ||||||
| 
 |  | ||||||
|         private const val IMAGE_DESCRAMBLER_JS = """ |  | ||||||
| (function() { |  | ||||||
|             var _deskeys = []; |  | ||||||
| _deskeys["60a2b0ed56cd458c4633d04b1b76b7e9"] = "18a72a69a64a13a1a43a3aa42a23a66a26a19a51a54a78a34a17a31a35a15a58a29a61a48a73a74a44a52a60a24a63a20a32a7a45a53a75a55a62a59a41a76a68a2a36a21a10a38a33a71a40a67a22a4a50a80a65a27a37a47a70a14a28a16a6a56a30a57a5a11a79a9a77a46a39a25a49a8a12"; |  | ||||||
| _deskeys["400df5e8817565e28b2e141c533ed7db"] = "61a74a10a45a3a37a72a22a57a39a25a56a52a29a70a60a67a41a63a55a27a28a43a18a5a9a8a40a17a48a44a79a38a47a32a73a4a6a13a34a33a49a2a42a50a76a54a36a35a14a58a7a69a46a16a30a21a11aa51a53a77a26a31a1a19a20a80a24a62a68a59a66a75a12a64a78a71a15a65a23"; |  | ||||||
| _deskeys["84ba0d8098f405b14f4dbbcc04c93bac"] = "61a26a35a16a55a10a72a37a2a60a66a65a33a44a7a28a70a62a32a56a30a40a58a15a74a47aa36a78a75a11a6a77a67a39a23a9a31a64a59a13a24a80a14a38a45a21a63a19a51a17a34a50a46a5a29a73a8a57a69a48a68a49a71a41a12a52a18a79a76a54a42a22a4a1a3a53a20a25a43a27"; |  | ||||||
| _deskeys["56665708741979f716e5bd64bf733c33"] = "23a7a41a48a57a27a69a36a76a62a40a75a26a2a51a6a10a65a43a24a1aa20a71a28a30a13a38a79a78a72a14a49a55a56a58a25a70a12a80a3a66a11a39a42a17a15a54a45a34a74a31a8a61a46a73a63a22a64a19a77a50a9a59a37a68a52a18a32a16a33a60a67a21a44a53a5a35a4a29a47"; |  | ||||||
| _deskeys.a67e15ed870fe4aab0a502478a5c720f = "8a12a59a52a24a13a37a21a55a56a41a71a65a43a40a66a11a79a67a44a33a20a72a2a31a42a29a34a58a60a27a48a28a15a35a51a76a80a0a63a69a53a39a46a64a50a75a1a57a9a62a74a18a16a73a14a17a6a19a61a23a38a10a3a32a26a36a54a4a30a45a47a70a22a7a68a49a77a5a25a78"; |  | ||||||
| _deskeys.b6a2f75185754b691e4dfe50f84db57c = "47a63a76a58a37a4a56a21a1a48a62a2a36a44a34a42a23a9a60a72a11a74a70a20a77a16a15a35a69a0a55a46a24a6a32a75a68a43a41a78a31a71a52a33a67a25a80a30a5a28a40a65a39a14a29a64a3a53a49a59a12a66a38a27a79a45a18a22a8a61a50a17a51a10a26a13a57a19a7a54a73"; |  | ||||||
| _deskeys.db99689c5a26a09d126c7089aedc0d86 = "57a31a46a61a55a41a26a2a39a24a75a4a45a13a23a51a15a8a64a37a72a34a12a3a79a42a80a17a62a49a19a77a48a68a78a65a14a10a29a16a20a76a38a36a54a30a53a40a33a21a44a22a32a5a1a7a70a67a58a0a71a74a43a66a6a63a35a56a73a9a27a25a59a47a52a11a50a18a28a60a69"; |  | ||||||
| _deskeys["37abcb7424ce8df47ccb1d2dd9144b49"] = "67a45a39a72a35a38a61a11a51a60a13a22a31a25a75a30a74a43a69a50a6a26a16a49a77a68a59a64a17a56a18a1a10a54a44a62a53a80a5a23a48a32a29a79a24a70a28a58a71a3a52a42a55a9a14a36a73a34a2a27a57a0a21a41a33a37a76a8a40a65a7a20a12a19a47a4a78a15a63a66a46"; |  | ||||||
| _deskeys["874b83ba76a7e783d13abc2dabc08d76"] = "26a59a42a43a4a20a61a28a12a64a37a52a2a77a34a13a46a74a70a0a44a29a73a66a55a38a69a67a62a9a63a6a54a79a21a33a8a58a40a47a71a49a22a50a57a78a56a25a17a15a36a16a48a32a5a10a14a80a24a72a76a45a3a53a23a41a60a11a65a19a27a51a68a35a31a1a75a39a30a7a18"; |  | ||||||
| _deskeys.d320d2647d70c068b89853e1a269c609 = "77a38a53a40a16a3a20a18a63a9a24a64a50a61a45a59a27a37a8a34a11a55a79a13a47a68a12a22a46a33a1a69a52a54a31a23a62a43a0a2a35a28a57a36a51a78a70a5a32a75a41a30a4a80a19a21a42a71a49a10a56a74a17a7a25a6a14a73a29a44a48a39a60a58a15a66a67a72a65a76a26"; |  | ||||||
| _deskeys["930b87ad89c2e2501f90d0f0e92a6b97"] = "9a29a49a67a62a40a28a50a64a77a46a31a16a73a14a45a51a44a7a76a22a78a68a37a74a69a25a65a41a11a52aa18a36a10a38a12a15a2a58a48a8a27a75a20a4a80a61a55a42a13a43a47a39a35a60a26a30a63a66a57a33a72a24a71a34a23a3a70a54a56a32a79a5a21a6a59a53a17a1a19"; |  | ||||||
| _deskeys.c587e77362502aaedad5b7cddfbe3a0d = "50aa59a70a68a30a56a10a49a43a45a29a23a28a61a15a40a71a14a44a32a34a17a26a63a76a75a33a74a12a11a21a67a31a19a80a7a64a8a3a51a53a38a18a6a42a27a9a52a20a41a60a1a22a77a16a54a47a79a24a78a2a46a37a73a65a36a35a39a5a4a25a72a13a62a55a57a58a69a66a48"; |  | ||||||
| _deskeys["1269606c6c3d8bb6508426468216d6b1"] = "49a15a0a60a14a26a34a69a61a24a35a4a77a80a70a40a39a6a68a17a41a56a28a46a79a16a21a1a37a42a44a58a78a18a52a73a32a9a12a50a8a13a20a19a67a36a45a75a48a10a65a7a38a66a3a2a43a27a29a31a72a74a55a23a54a22a59a57a11a62a47a53a30a5a64a25a76a71a51a33a63"; |  | ||||||
| _deskeys["33a3b21bb2d14a09d15f995224ae4284"] = "30a59a35a34a42a8a10a56a70a64a48a69a26a18a6a16a54a24a73a79a68a33a32a2a63a53a31a14a17a57a41a80a76a40a60a12a43a29a39a4a77a58a66a36a38a52a13a19a0a75a28a55a25a61a71a11a67a49a23a45a5a15a1a50a51a9a44a47a65a74a72a27a7a37a46a20a22a62a78a21a3"; |  | ||||||
| _deskeys["9ae6640761b947e61624671ef841ee78"] = "62a25a21a75a42a61a73a59a23a19a66a38a71a70a6a55a3a16a43a32a53a37a41a28a49a63a47a17a7a30a78a46a20a67a56a79a65a14a69a60a8a52a22a9a24a2a4a13a36a27a0a18a33a12a44a5a76a26a29a40a1a11a64a48a39a51a80a72a68a10a58a35a77a54a34a74a57a31a50a45a15"; |  | ||||||
| _deskeys.f4ab0903149b5d94baba796a5cf05938 = "40a37a55a73a18a42a15a59a50a13a22a63a52a58a6a80a47a17a38a71a74a70a30a11a10a19a0a31a36a21a51a68a1a3a14a66a45a2a79a7a76a75a8a67a20a78a25a69a43a28a35a60a4a23a65a54a34a9a5a39a27a57a26a33a12a24a46a72a56a44a49a61a64a29a53a48a32a62a41a16a77"; |  | ||||||
| _deskeys.f5baf770212313f5e9532ec5e6103b61 = "55a69a78a75a38a25a20a60a6a80a46a5a48a18a23a24a17a67a64a70a63a57a22a10a49a19a8a16a11a12a61a76a34a27a54a73a44a0a56a3a15a29a28a13a4a2a7a77a74a35a37a26a30a58a9a71a50a1a43a79a47a32a14a53a52a66a72a59a68a31a42a45a62a51a40a39a33a65a41a36a21"; |  | ||||||
| _deskeys.e2169a4bfd805e9aa21d3112d498d68c = "54a34a68a69a26a20a66a1a67a74a22a39a63a70a5a37a75a15a6a14a62a50a46a35a44a45a28a8a40a25a29a76a51a77a17a47a0a42a2a9a48a27a13a64a58a57a18a30a80a23a61a36a60a59a71a32a7a38a41a78a12a49a43a79a24a31a52a19a3a53a72a10a73a11a33a16a4a55a65a21a56"; |  | ||||||
| _deskeys['1796550d20f64decb317f9b770ba0e78'] = '37a55a39a79a2a53a75a1a30a32a3a13a25a49a45a5a60a62a71a78a63a24a27a33a19a64a67a57a0a8a54a9a41a61a50a73a7a65a58a51a15a14a43a4a35a77a68a72a34a80a22a17a48a10a70a46a40a28a20a74a52a23a38a76a42a18a66a11a59a6a69a31a56a16a47a21a12a44a36a29a26'; |  | ||||||
| _deskeys['bf53be6753a0037c6d80ca670f5d12d5'] = '55a41a18a19a4a13a36a12a56a69a64a80a30a39a57a50a48a26a46a73a17a52a49a66a11a25a61a51a68a24a70a7a67a53a43a8a29a75a65a42a38a58a9a28a0a78a54a31a22a5a15a3a79a77a59a23a45a40a47a44a6a2a1a35a14a62a63a76a20a16a32a21a71a10a74a60a34a37a33a72a27'; |  | ||||||
| _deskeys['6c41ff7fbed622aa76e19f3564e5d52a'] = '40a3a13a59a68a34a66a43a67a14a26a46a8a24a33a73a69a31a2a57a10a51a62a77a74a41a47a35a64a52a15a53a6a80a76a50a28a75a56a79a17a45a25a49a48a65a78a27a9a63a12a55a32a21a58a38a0a71a44a30a61a36a16a23a20a70a22a37a4a19a7a60a11a5a18a39a1a54a72a29a42'; |  | ||||||
| var l = "18a72a69a64a13a1a43a3aa42a23a66a26a19a51a54a78a34a17a31a35a15a58a29a61a48a73a74a44a52a60a24a63a20a32a7a45a53a75a55a62a59a41a76a68a2a36a21a10a38a33a71a40a67a22a4a50a80a65a27a37a47a70a14a28a16a6a56a30a57a5a11a79a9a77a46a39a25a49a8a12"; |  | ||||||
|   for (mk in _deskeys) { |  | ||||||
|     if (img.indexOf(mk) > 0) { |  | ||||||
|       l = _deskeys[mk]; |  | ||||||
|       break; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   l = l.split("a"); |  | ||||||
|   var cols = heightnum = 9; |  | ||||||
|   var w = width / cols; |  | ||||||
|   var h = height / heightnum; |  | ||||||
|   var r; |  | ||||||
|   var y; |  | ||||||
|   var x; |  | ||||||
|   var py; |  | ||||||
|   var canvasX; |  | ||||||
|   var i = 0; |  | ||||||
|   var result = ""; |  | ||||||
|   for (;i < cols * heightnum;i++) { |  | ||||||
|     r = Math.floor(l[i] / heightnum); |  | ||||||
|     y = r * h; |  | ||||||
|     x = (l[i] - r * cols) * w; |  | ||||||
|     r = Math.floor(i / cols); |  | ||||||
|     py = r * h; |  | ||||||
|     canvasX = (i - r * cols) * w; |  | ||||||
|     result += " " + canvasX + "," + py + "," + w + "," + h + "," + x + "," + y; |  | ||||||
|   } |  | ||||||
|   return result; |  | ||||||
| })(); |  | ||||||
|             """ |  | ||||||
| 
 |  | ||||||
|         // Allow destructuring up to 6 items for lists |  | ||||||
|         private operator fun <T> List<T>.component6() = get(5) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,12 +0,0 @@ | |||||||
| apply plugin: 'com.android.application' |  | ||||||
| apply plugin: 'kotlin-android' |  | ||||||
| 
 |  | ||||||
| ext { |  | ||||||
|     appName = 'Tachiyomi: Manga Plus by Shueisha' |  | ||||||
|     pkgNameSuffix = 'en.mangaplus' |  | ||||||
|     extClass = '.MangaPlus' |  | ||||||
|     extVersionCode = 4 |  | ||||||
|     libVersion = '1.2' |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| apply from: "$rootDir/common.gradle" |  | ||||||
| Before Width: | Height: | Size: 4.1 KiB | 
| Before Width: | Height: | Size: 2.2 KiB | 
| Before Width: | Height: | Size: 5.9 KiB | 
| Before Width: | Height: | Size: 10 KiB | 
| Before Width: | Height: | Size: 18 KiB | 
| Before Width: | Height: | Size: 56 KiB | 
| @ -1,30 +0,0 @@ | |||||||
| package eu.kanade.tachiyomi.extension.en.mangaplus |  | ||||||
| 
 |  | ||||||
| import eu.kanade.tachiyomi.source.model.* |  | ||||||
| import eu.kanade.tachiyomi.source.online.HttpSource |  | ||||||
| import okhttp3.Response |  | ||||||
| 
 |  | ||||||
| class MangaPlus : HttpSource() { |  | ||||||
|     override val name = "Manga Plus by Shueisha" |  | ||||||
|      |  | ||||||
|     override val baseUrl = "https://jumpg-webapi.tokyo-cdn.com/api" |  | ||||||
|      |  | ||||||
|     override val lang = "en" |  | ||||||
|      |  | ||||||
|     override val supportsLatest = false |  | ||||||
|      |  | ||||||
|     override fun popularMangaRequest(page: Int) = throw Exception(NEED_MIGRATION) |  | ||||||
|     override fun popularMangaParse(response: Response) = throw Exception(NEED_MIGRATION) |  | ||||||
|     override fun latestUpdatesRequest(page: Int) = throw Exception(NEED_MIGRATION) |  | ||||||
|     override fun latestUpdatesParse(response: Response) = throw Exception(NEED_MIGRATION) |  | ||||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw Exception(NEED_MIGRATION) |  | ||||||
|     override fun searchMangaParse(response: Response) = throw Exception(NEED_MIGRATION) |  | ||||||
|     override fun mangaDetailsParse(response: Response) = throw Exception(NEED_MIGRATION) |  | ||||||
|     override fun chapterListParse(response: Response) = throw Exception(NEED_MIGRATION) |  | ||||||
|     override fun pageListParse(response: Response) = throw Exception(NEED_MIGRATION) |  | ||||||
|     override fun imageUrlParse(response: Response) = throw Exception(NEED_MIGRATION) |  | ||||||
|      |  | ||||||
|     companion object { |  | ||||||
|         const val NEED_MIGRATION = "Deprecated: Use 'All MangaPlus' instead.\nSource migration is on 'My Library' -> three dots -> 'Source migration'" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,12 +0,0 @@ | |||||||
| apply plugin: 'com.android.application' |  | ||||||
| apply plugin: 'kotlin-android' |  | ||||||
| 
 |  | ||||||
| ext { |  | ||||||
|     appName = 'Tachiyomi: Manhwahentai' |  | ||||||
|     pkgNameSuffix = 'en.manhwahentai' |  | ||||||
|     extClass = '.Manhwahentai' |  | ||||||
|     extVersionCode = 2 |  | ||||||
|     libVersion = '1.2' |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| apply from: "$rootDir/common.gradle" |  | ||||||
| Before Width: | Height: | Size: 3.1 KiB | 
| Before Width: | Height: | Size: 2.2 KiB | 
| Before Width: | Height: | Size: 4.4 KiB | 
| Before Width: | Height: | Size: 6.0 KiB | 
| Before Width: | Height: | Size: 10 KiB | 
| Before Width: | Height: | Size: 24 KiB | 
| @ -1,253 +0,0 @@ | |||||||
| package eu.kanade.tachiyomi.extension.en.manhwahentai |  | ||||||
| 
 |  | ||||||
| import eu.kanade.tachiyomi.network.GET |  | ||||||
| import eu.kanade.tachiyomi.source.online.ParsedHttpSource |  | ||||||
| import okhttp3.* |  | ||||||
| import org.jsoup.nodes.Document |  | ||||||
| import org.jsoup.nodes.Element |  | ||||||
| import java.text.SimpleDateFormat |  | ||||||
| import java.util.* |  | ||||||
| import eu.kanade.tachiyomi.source.model.* |  | ||||||
| import java.text.ParseException |  | ||||||
| 
 |  | ||||||
| class Manhwahentai: ParsedHttpSource() { |  | ||||||
| 
 |  | ||||||
|     override val name = "Manhwahentai" |  | ||||||
|     override val baseUrl = "https://manhwahentai.com" |  | ||||||
|     override val lang = "en" |  | ||||||
|     override val supportsLatest = true |  | ||||||
|     override val client: OkHttpClient = network.cloudflareClient |  | ||||||
| 
 |  | ||||||
|     override fun popularMangaRequest(page: Int): Request { |  | ||||||
|         return GET("$baseUrl/page/$page?s&post_type=wp-manga&m_orderby=views", headers) |  | ||||||
|     } |  | ||||||
|     override fun latestUpdatesRequest(page: Int): Request { |  | ||||||
|         return GET("$baseUrl/page/$page?s&post_type=wp-manga&m_orderby=latest", headers) |  | ||||||
|     } |  | ||||||
|     //    LIST SELECTOR |  | ||||||
|     override fun popularMangaSelector() = "div.c-tabs-item__content" |  | ||||||
|     override fun latestUpdatesSelector() =  popularMangaSelector() |  | ||||||
|     override fun searchMangaSelector() = popularMangaSelector() |  | ||||||
| 
 |  | ||||||
|     //    ELEMENT |  | ||||||
|     override fun popularMangaFromElement(element: Element): SManga =  searchMangaFromElement(element) |  | ||||||
|     override fun latestUpdatesFromElement(element: Element): SManga =  searchMangaFromElement(element) |  | ||||||
| 
 |  | ||||||
|     //    NEXT SELECTOR |  | ||||||
|     override fun popularMangaNextPageSelector() = "div.nav-previous.float-left > a" |  | ||||||
|     override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() |  | ||||||
|     override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() |  | ||||||
| 
 |  | ||||||
|     override fun searchMangaFromElement(element: Element):SManga { |  | ||||||
|         val manga = SManga.create() |  | ||||||
|         manga.thumbnail_url = element.select("div.tab-thumb > a > img").attr("src") |  | ||||||
|         element.select("div.tab-thumb > a").first().let { |  | ||||||
|             manga.setUrlWithoutDomain(it.attr("href")) |  | ||||||
|             manga.title = it.attr("title") |  | ||||||
|         } |  | ||||||
|         return manga |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { |  | ||||||
|         val url = HttpUrl.parse("$baseUrl/page/$page")!!.newBuilder() |  | ||||||
|         url.addQueryParameter("post_type","wp-manga") |  | ||||||
|         val pattern = "\\s+".toRegex() |  | ||||||
|         val q = query.replace(pattern, "+") |  | ||||||
|         if(query.length > 0){ |  | ||||||
|             url.addQueryParameter("s", q) |  | ||||||
|         }else{ |  | ||||||
|             url.addQueryParameter("s", "") |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         var orderBy = "" |  | ||||||
| 
 |  | ||||||
|         (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> |  | ||||||
|             when (filter) { |  | ||||||
| //                is Status -> url.addQueryParameter("manga_status", arrayOf("", "completed", "ongoing")[filter.state]) |  | ||||||
|                 is GenreList -> { |  | ||||||
|                     val genreInclude = mutableListOf<String>() |  | ||||||
|                     filter.state.forEach { |  | ||||||
|                         if (it.state == 1) { |  | ||||||
|                             genreInclude.add(it.id) |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     if(genreInclude.isNotEmpty()){ |  | ||||||
|                         genreInclude.forEach{ genre -> |  | ||||||
|                             url.addQueryParameter("genre[]", genre) |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 is StatusList ->{ |  | ||||||
|                     val statuses = mutableListOf<String>() |  | ||||||
|                     filter.state.forEach { |  | ||||||
|                         if (it.state == 1) { |  | ||||||
|                             statuses.add(it.id) |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     if(statuses.isNotEmpty()){ |  | ||||||
|                         statuses.forEach{ status -> |  | ||||||
|                             url.addQueryParameter("status[]", status) |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 is SortBy -> { |  | ||||||
|                     orderBy = filter.toUriPart(); |  | ||||||
|                     url.addQueryParameter("m_orderby",orderBy) |  | ||||||
|                 } |  | ||||||
|                 is TextField -> url.addQueryParameter(filter.key, filter.state) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return GET(url.toString(), headers) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     // max 200 results |  | ||||||
| 
 |  | ||||||
|     override fun mangaDetailsParse(document: Document): SManga { |  | ||||||
|         val infoElement = document.select("div.site-content").first() |  | ||||||
| 
 |  | ||||||
|         val manga = SManga.create() |  | ||||||
|         manga.author = infoElement.select("div.author-content")?.text() |  | ||||||
|         manga.artist = infoElement.select("div.artist-content")?.text() |  | ||||||
| 
 |  | ||||||
|         val genres = mutableListOf<String>() |  | ||||||
|         infoElement.select("div.genres-content a").forEach { element -> |  | ||||||
|             val genre = element.text() |  | ||||||
|             genres.add(genre) |  | ||||||
|         } |  | ||||||
|         manga.genre =genres.joinToString(", ") |  | ||||||
|         manga.status = parseStatus(infoElement.select("div.post-status > div:nth-child(2)  div").text()) |  | ||||||
| 
 |  | ||||||
|         manga.description = document.select("div.summary__content > p")?.text() |  | ||||||
|         manga.thumbnail_url = document.select("div.summary_image > a > img").attr("src") |  | ||||||
| 
 |  | ||||||
|         return manga |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private fun parseStatus(element: String): Int = when { |  | ||||||
| 
 |  | ||||||
|         element.toLowerCase().contains("ongoing") -> SManga.ONGOING |  | ||||||
|         element.toLowerCase().contains("completed") -> SManga.COMPLETED |  | ||||||
|         else -> SManga.UNKNOWN |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun chapterListSelector() = "li.wp-manga-chapter" |  | ||||||
| 
 |  | ||||||
|     override fun chapterFromElement(element: Element): SChapter { |  | ||||||
|         val urlElement = element.select("a").first() |  | ||||||
|         val chapter = SChapter.create() |  | ||||||
|         chapter.setUrlWithoutDomain(urlElement.attr("href")) |  | ||||||
|         chapter.name = urlElement.text() |  | ||||||
|         chapter.date_upload = element.select("span.chapter-release-date i").last()?.text()?.let { |  | ||||||
|             try { |  | ||||||
|                 SimpleDateFormat("MMMM dd, yyyy", Locale.US).parse(it).time |  | ||||||
|             } catch (e: ParseException) { |  | ||||||
|                 SimpleDateFormat("MMM dd, yyyy", Locale.US).parse(it).time |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|         } ?: 0 |  | ||||||
|         return chapter |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun prepareNewChapter(chapter: SChapter, manga: SManga) { |  | ||||||
|         val basic = Regex("""Chapter\s([0-9]+)""") |  | ||||||
|         when { |  | ||||||
|             basic.containsMatchIn(chapter.name) -> { |  | ||||||
|                 basic.find(chapter.name)?.let { |  | ||||||
|                     chapter.chapter_number = it.groups[1]?.value!!.toFloat() |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun pageListParse(document: Document): List<Page> { |  | ||||||
|         val pages = mutableListOf<Page>() |  | ||||||
|         var i = 0 |  | ||||||
|         document.select("div.reading-content * img").forEach { element -> |  | ||||||
|             val url = element.attr("src") |  | ||||||
|             i++ |  | ||||||
|             if(url.length != 0){ |  | ||||||
|                 pages.add(Page(i, "", url)) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return pages |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun imageUrlParse(document: Document) = "" |  | ||||||
| 
 |  | ||||||
|     override fun imageRequest(page: Page): Request { |  | ||||||
|         val imgHeader = Headers.Builder().apply { |  | ||||||
|             add("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.1.1; en-gb; Build/KLP) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30") |  | ||||||
|             add("Referer", baseUrl) |  | ||||||
|         }.build() |  | ||||||
|         return GET(page.imageUrl!!, imgHeader) |  | ||||||
|     } |  | ||||||
|     //    private class Status : Filter.TriState("Completed") |  | ||||||
|     private class TextField(name: String, val key: String) : Filter.Text(name) |  | ||||||
|     private class SortBy : UriPartFilter("Sort by", arrayOf( |  | ||||||
|             Pair("Relevance", ""), |  | ||||||
|             Pair("Latest", "latest"), |  | ||||||
|             Pair("A-Z", "alphabet"), |  | ||||||
|             Pair("Rating", "rating"), |  | ||||||
|             Pair("Trending", "trending"), |  | ||||||
|             Pair("Most View", "views"), |  | ||||||
|             Pair("New", "new-manga") |  | ||||||
|     )) |  | ||||||
|     private class Genre(name: String, val id: String = name) : Filter.TriState(name) |  | ||||||
|     private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) |  | ||||||
|     private class Status(name: String, val id: String = name) : Filter.TriState(name) |  | ||||||
|     private class StatusList(statuses: List<Status>) : Filter.Group<Status>("Status", statuses) |  | ||||||
| 
 |  | ||||||
|     override fun getFilterList() = FilterList( |  | ||||||
| //            TextField("Judul", "title"), |  | ||||||
|             TextField("Author", "author"), |  | ||||||
|             TextField("Year", "release"), |  | ||||||
|             SortBy(), |  | ||||||
|             StatusList(getStatusList()), |  | ||||||
|             GenreList(getGenreList()) |  | ||||||
|     ) |  | ||||||
|     private fun getStatusList() = listOf( |  | ||||||
|             Status("Completed","end"), |  | ||||||
|             Status("Ongoing","on-going"), |  | ||||||
|             Status("Canceled","canceled"), |  | ||||||
|             Status("Onhold","on-hold") |  | ||||||
|     ) |  | ||||||
|     private fun getGenreList() = listOf( |  | ||||||
|             Genre("Action","action"), |  | ||||||
|             Genre("Adventure","adventure"), |  | ||||||
|             Genre("Comedy","comedy"), |  | ||||||
|             Genre("Drama","drama"), |  | ||||||
|             Genre("Fantasy","fantasy"), |  | ||||||
|             Genre("Gender bender","gender-bender"), |  | ||||||
|             Genre("Gossip","gossip"), |  | ||||||
|             Genre("Harem","harem"), |  | ||||||
|             Genre("Historical","historical"), |  | ||||||
|             Genre("Horror","horror"), |  | ||||||
|             Genre("Incest","incest"), |  | ||||||
|             Genre("Martial arts","martial-arts"), |  | ||||||
|             Genre("Mecha","mecha"), |  | ||||||
|             Genre("Medical","medical"), |  | ||||||
|             Genre("Mystery","mystery"), |  | ||||||
|             Genre("One shot","one-shot"), |  | ||||||
|             Genre("Psychological","psychological"), |  | ||||||
|             Genre("Romance","romance"), |  | ||||||
|             Genre("School LIfe","school-life"), |  | ||||||
|             Genre("Sci Fi","sci-fi"), |  | ||||||
|             Genre("Slice of Life","slice-of-life"), |  | ||||||
|             Genre("Smut","smut"), |  | ||||||
|             Genre("Sports","sports"), |  | ||||||
|             Genre("Supernatural","supernatural"), |  | ||||||
|             Genre("Tragedy","tragedy"), |  | ||||||
|             Genre("Yaoi","yaoi"), |  | ||||||
|             Genre("Yuri","yuri") |  | ||||||
|     ) |  | ||||||
|     private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : |  | ||||||
|             Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { |  | ||||||
|         fun toUriPart() = vals[state].second |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
 Eugene
						Eugene