mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Remove internal sources
This commit is contained in:
		| @@ -6,11 +6,6 @@ import eu.kanade.tachiyomi.source.model.Page | ||||
| import eu.kanade.tachiyomi.source.model.SChapter | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import eu.kanade.tachiyomi.source.online.english.* | ||||
| import eu.kanade.tachiyomi.source.online.german.WieManga | ||||
| import eu.kanade.tachiyomi.source.online.russian.Mangachan | ||||
| import eu.kanade.tachiyomi.source.online.russian.Mintmanga | ||||
| import eu.kanade.tachiyomi.source.online.russian.Readmanga | ||||
| import rx.Observable | ||||
|  | ||||
| open class SourceManager(private val context: Context) { | ||||
| @@ -48,17 +43,7 @@ open class SourceManager(private val context: Context) { | ||||
|     } | ||||
|  | ||||
|     private fun createInternalSources(): List<Source> = listOf( | ||||
|             LocalSource(context), | ||||
|             Batoto(), | ||||
|             Mangahere(), | ||||
|             Mangafox(), | ||||
|             Kissmanga(), | ||||
|             Readmanga(), | ||||
|             Mintmanga(), | ||||
|             Mangachan(), | ||||
|             Readmangatoday(), | ||||
|             Mangasee(), | ||||
|             WieManga() | ||||
|             LocalSource(context) | ||||
|     ) | ||||
|  | ||||
|     private inner class StubSource(override val id: Long) : Source { | ||||
|   | ||||
| @@ -1,31 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.source.online.english | ||||
|  | ||||
| import eu.kanade.tachiyomi.source.Source | ||||
| import eu.kanade.tachiyomi.source.model.Page | ||||
| import eu.kanade.tachiyomi.source.model.SChapter | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import rx.Observable | ||||
|  | ||||
| class Batoto : Source { | ||||
|  | ||||
|     override val id: Long = 1 | ||||
|  | ||||
|     override val name = "Batoto" | ||||
|  | ||||
|     override fun fetchMangaDetails(manga: SManga): Observable<SManga> { | ||||
|         return Observable.error(Exception("RIP Batoto")) | ||||
|     } | ||||
|  | ||||
|     override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { | ||||
|         return Observable.error(Exception("RIP Batoto")) | ||||
|     } | ||||
|  | ||||
|     override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { | ||||
|         return Observable.error(Exception("RIP Batoto")) | ||||
|     } | ||||
|  | ||||
|     override fun toString(): String { | ||||
|         return "$name (EN)" | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,253 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.source.online.english | ||||
|  | ||||
| import com.squareup.duktape.Duktape | ||||
| 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 okhttp3.FormBody | ||||
| import okhttp3.Headers | ||||
| import okhttp3.OkHttpClient | ||||
| import okhttp3.Request | ||||
| import okhttp3.Response | ||||
| import org.jsoup.nodes.Document | ||||
| import org.jsoup.nodes.Element | ||||
| import timber.log.Timber | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.regex.Pattern | ||||
|  | ||||
| class Kissmanga : ParsedHttpSource() { | ||||
|  | ||||
|     override val id: Long = 4 | ||||
|  | ||||
|     override val name = "Kissmanga" | ||||
|  | ||||
|     override val baseUrl = "http://kissmanga.com" | ||||
|  | ||||
|     override val lang = "en" | ||||
|  | ||||
|     override val supportsLatest = true | ||||
|  | ||||
|     override val client: OkHttpClient = network.cloudflareClient | ||||
|  | ||||
|     override fun headersBuilder(): Headers.Builder { | ||||
|         return Headers.Builder() | ||||
|             .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) Gecko/20100101 Firefox/60") | ||||
|     } | ||||
|  | ||||
|     override fun popularMangaSelector() = "table.listing tr:gt(1)" | ||||
|  | ||||
|     override fun latestUpdatesSelector() = "table.listing tr:gt(1)" | ||||
|  | ||||
|     override fun popularMangaRequest(page: Int): Request { | ||||
|         return GET("$baseUrl/MangaList/MostPopular?page=$page", headers) | ||||
|     } | ||||
|  | ||||
|     override fun latestUpdatesRequest(page: Int): Request { | ||||
|         return GET("http://kissmanga.com/MangaList/LatestUpdate?page=$page", headers) | ||||
|     } | ||||
|  | ||||
|     override fun popularMangaFromElement(element: Element): SManga { | ||||
|         val manga = SManga.create() | ||||
|         element.select("td a:eq(0)").first().let { | ||||
|             manga.setUrlWithoutDomain(it.attr("href")) | ||||
|             val title = it.text() | ||||
|             //check if cloudfire email obfuscation is affecting title name | ||||
|             if (title.contains("[email protected]", true)) { | ||||
|                 try { | ||||
|                     var str: String = it.html() | ||||
|                     //get the  number | ||||
|                     str = str.substringAfter("data-cfemail=\"") | ||||
|                     str = str.substringBefore("\">[email") | ||||
|                     val sb = StringBuilder() | ||||
|                     //convert number to char | ||||
|                     val r = Integer.valueOf(str.substring(0, 2), 16)!! | ||||
|                     var i = 2 | ||||
|                     while (i < str.length) { | ||||
|                         val c = (Integer.valueOf(str.substring(i, i + 2), 16) xor r).toChar() | ||||
|                         sb.append(c) | ||||
|                         i += 2 | ||||
|                     } | ||||
|                     //replace the new word into the title | ||||
|                     manga.title = title.replace("[email protected]", sb.toString(), true) | ||||
|                 } catch (e: Exception) { | ||||
|                     //on error just default to obfuscated title | ||||
|                     Timber.e("error parsing [email protected]", e) | ||||
|                     manga.title = title | ||||
|                 } | ||||
|             } else { | ||||
|                 manga.title = title | ||||
|             } | ||||
|         } | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     override fun latestUpdatesFromElement(element: Element): SManga { | ||||
|         return popularMangaFromElement(element) | ||||
|     } | ||||
|  | ||||
|     override fun popularMangaNextPageSelector() = "li > a:contains(› Next)" | ||||
|  | ||||
|     override fun latestUpdatesNextPageSelector(): String = "ul.pager > li > a:contains(Next)" | ||||
|  | ||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { | ||||
|         val form = FormBody.Builder().apply { | ||||
|             add("mangaName", query) | ||||
|  | ||||
|             for (filter in if (filters.isEmpty()) getFilterList() else filters) { | ||||
|                 when (filter) { | ||||
|                     is Author -> add("authorArtist", filter.state) | ||||
|                     is Status -> add("status", arrayOf("", "Completed", "Ongoing")[filter.state]) | ||||
|                     is GenreList -> filter.state.forEach { genre -> add("genres", genre.state.toString()) } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return POST("$baseUrl/AdvanceSearch", headers, form.build()) | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaSelector() = popularMangaSelector() | ||||
|  | ||||
|     override fun searchMangaFromElement(element: Element): SManga { | ||||
|         return popularMangaFromElement(element) | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaNextPageSelector() = null | ||||
|  | ||||
|     override fun mangaDetailsParse(document: Document): SManga { | ||||
|         val infoElement = document.select("div.barContent").first() | ||||
|  | ||||
|         val manga = SManga.create() | ||||
|         manga.author = infoElement.select("p:has(span:contains(Author:)) > a").first()?.text() | ||||
|         manga.genre = infoElement.select("p:has(span:contains(Genres:)) > *:gt(0)").text() | ||||
|         manga.description = infoElement.select("p:has(span:contains(Summary:)) ~ p").text() | ||||
|         manga.status = infoElement.select("p:has(span:contains(Status:))").first()?.text().orEmpty().let { parseStatus(it) } | ||||
|         manga.thumbnail_url = document.select(".rightBox:eq(0) img").first()?.attr("src") | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     fun parseStatus(status: String) = when { | ||||
|         status.contains("Ongoing") -> SManga.ONGOING | ||||
|         status.contains("Completed") -> SManga.COMPLETED | ||||
|         else -> SManga.UNKNOWN | ||||
|     } | ||||
|  | ||||
|     override fun chapterListSelector() = "table.listing tr:gt(1)" | ||||
|  | ||||
|     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("td:eq(1)").first()?.text()?.let { | ||||
|             SimpleDateFormat("MM/dd/yyyy").parse(it).time | ||||
|         } ?: 0 | ||||
|         return chapter | ||||
|     } | ||||
|  | ||||
|     override fun pageListRequest(chapter: SChapter) = POST(baseUrl + chapter.url, headers) | ||||
|  | ||||
|     override fun pageListParse(response: Response): List<Page> { | ||||
|         val body = response.body()!!.string() | ||||
|  | ||||
|         val pages = mutableListOf<Page>() | ||||
|  | ||||
|         // Kissmanga now encrypts the urls, so we need to execute these two scripts in JS. | ||||
|         val ca = client.newCall(GET("$baseUrl/Scripts/ca.js", headers)).execute().body()!!.string() | ||||
|         val lo = client.newCall(GET("$baseUrl/Scripts/lo.js", headers)).execute().body()!!.string() | ||||
|  | ||||
|         Duktape.create().use { | ||||
|             it.evaluate(ca) | ||||
|             it.evaluate(lo) | ||||
|  | ||||
|             // There are two functions in an inline script needed to decrypt the urls. We find and | ||||
|             // execute them. | ||||
|             var p = Pattern.compile("(var.*CryptoJS.*)") | ||||
|             var m = p.matcher(body) | ||||
|             while (m.find()) { | ||||
|                 it.evaluate(m.group(1)) | ||||
|             } | ||||
|  | ||||
|             // Finally find all the urls and decrypt them in JS. | ||||
|             p = Pattern.compile("""lstImages.push\((.*)\);""") | ||||
|             m = p.matcher(body) | ||||
|  | ||||
|             var i = 0 | ||||
|             while (m.find()) { | ||||
|                 val url = it.evaluate(m.group(1)) as String | ||||
|                 pages.add(Page(i++, "", url)) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         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) = "" | ||||
|  | ||||
|     private class Status : Filter.TriState("Completed") | ||||
|     private class Author : Filter.Text("Author") | ||||
|     private class Genre(name: String) : Filter.TriState(name) | ||||
|     private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) | ||||
|  | ||||
|     override fun getFilterList() = FilterList( | ||||
|             Author(), | ||||
|             Status(), | ||||
|             GenreList(getGenreList()) | ||||
|     ) | ||||
|  | ||||
|     // $("select[name=\"genres\"]").map((i,el) => `Genre("${$(el).next().text().trim()}", ${i})`).get().join(',\n') | ||||
|     // on http://kissmanga.com/AdvanceSearch | ||||
|     private fun getGenreList() = listOf( | ||||
|             Genre("4-Koma"), | ||||
|             Genre("Action"), | ||||
|             Genre("Adult"), | ||||
|             Genre("Adventure"), | ||||
|             Genre("Comedy"), | ||||
|             Genre("Comic"), | ||||
|             Genre("Cooking"), | ||||
|             Genre("Doujinshi"), | ||||
|             Genre("Drama"), | ||||
|             Genre("Ecchi"), | ||||
|             Genre("Fantasy"), | ||||
|             Genre("Gender Bender"), | ||||
|             Genre("Harem"), | ||||
|             Genre("Historical"), | ||||
|             Genre("Horror"), | ||||
|             Genre("Josei"), | ||||
|             Genre("Lolicon"), | ||||
|             Genre("Manga"), | ||||
|             Genre("Manhua"), | ||||
|             Genre("Manhwa"), | ||||
|             Genre("Martial Arts"), | ||||
|             Genre("Mature"), | ||||
|             Genre("Mecha"), | ||||
|             Genre("Medical"), | ||||
|             Genre("Music"), | ||||
|             Genre("Mystery"), | ||||
|             Genre("One shot"), | ||||
|             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("Webtoon"), | ||||
|             Genre("Yaoi"), | ||||
|             Genre("Yuri") | ||||
|     ) | ||||
| } | ||||
| @@ -1,231 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.source.online.english | ||||
|  | ||||
| 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://mangafox.la" | ||||
|  | ||||
|     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,259 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.source.online.english | ||||
|  | ||||
| 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.security.SecureRandom | ||||
| import java.security.cert.X509Certificate | ||||
| import java.text.ParseException | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.* | ||||
| import javax.net.ssl.SSLContext | ||||
| import javax.net.ssl.X509TrustManager | ||||
|  | ||||
| class Mangahere : ParsedHttpSource() { | ||||
|  | ||||
|     override val id: Long = 2 | ||||
|  | ||||
|     override val name = "Mangahere" | ||||
|  | ||||
|     override val baseUrl = "http://www.mangahere.cc" | ||||
|  | ||||
|     override val lang = "en" | ||||
|  | ||||
|     override val supportsLatest = true | ||||
|  | ||||
|     private val trustManager = object : X509TrustManager { | ||||
|         override fun getAcceptedIssuers(): Array<X509Certificate> { | ||||
|             return emptyArray() | ||||
|         } | ||||
|  | ||||
|         override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) { | ||||
|         } | ||||
|  | ||||
|         override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private val sslContext = SSLContext.getInstance("SSL").apply { | ||||
|         init(null, arrayOf(trustManager), SecureRandom()) | ||||
|     } | ||||
|  | ||||
|     override val client = super.client.newBuilder() | ||||
|             .sslSocketFactory(sslContext.socketFactory, trustManager) | ||||
|             .build() | ||||
|  | ||||
|     override fun popularMangaSelector() = "div.directory_list > ul > li" | ||||
|  | ||||
|     override fun latestUpdatesSelector() = "div.directory_list > ul > li" | ||||
|  | ||||
|     override fun popularMangaRequest(page: Int): Request { | ||||
|         return GET("$baseUrl/directory/$page.htm?views.za", headers) | ||||
|     } | ||||
|  | ||||
|     override fun latestUpdatesRequest(page: Int): Request { | ||||
|         return GET("$baseUrl/directory/$page.htm?last_chapter_time.za", headers) | ||||
|     } | ||||
|  | ||||
|     private fun mangaFromElement(query: String, element: Element): SManga { | ||||
|         val manga = SManga.create() | ||||
|         element.select(query).first().let { | ||||
|             manga.setUrlWithoutDomain(it.attr("href")) | ||||
|             manga.title = if (it.hasAttr("title")) it.attr("title") else if (it.hasAttr("rel")) it.attr("rel") else it.text() | ||||
|         } | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     override fun popularMangaFromElement(element: Element): SManga { | ||||
|         return mangaFromElement("div.title > a", element) | ||||
|     } | ||||
|  | ||||
|     override fun latestUpdatesFromElement(element: Element): SManga { | ||||
|         return popularMangaFromElement(element) | ||||
|     } | ||||
|  | ||||
|     override fun popularMangaNextPageSelector() = "div.next-page > a.next" | ||||
|  | ||||
|     override fun latestUpdatesNextPageSelector() = "div.next-page > a.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("is_completed", arrayOf("", "1", "0")[filter.state]) | ||||
|                 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("direction", arrayOf("", "rl", "lr")[filter.state]) | ||||
|                 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.result_search > dl:has(dt)" | ||||
|  | ||||
|     override fun searchMangaFromElement(element: Element): SManga { | ||||
|         return mangaFromElement("a.manga_info", element) | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaNextPageSelector() = "div.next-page > a.next" | ||||
|  | ||||
|     override fun mangaDetailsParse(document: Document): SManga { | ||||
|         val detailElement = document.select(".manga_detail_top").first() | ||||
|         val infoElement = detailElement.select(".detail_topText").first() | ||||
|         val licensedElement = document.select(".mt10.color_ff00.mb10").first() | ||||
|  | ||||
|         val manga = SManga.create() | ||||
|         manga.author = infoElement.select("a[href*=author/]").first()?.text() | ||||
|         manga.artist = infoElement.select("a[href*=artist/]").first()?.text() | ||||
|         manga.genre = infoElement.select("li:eq(3)").first()?.text()?.substringAfter("Genre(s):") | ||||
|         manga.description = infoElement.select("#show").first()?.text()?.substringBeforeLast("Show less") | ||||
|         manga.thumbnail_url = detailElement.select("img.img").first()?.attr("src") | ||||
|  | ||||
|         if (licensedElement?.text()?.contains("licensed") == true) { | ||||
|             manga.status = SManga.LICENSED | ||||
|         } else { | ||||
|             manga.status = infoElement.select("li:eq(6)").first()?.text().orEmpty().let { parseStatus(it) } | ||||
|         } | ||||
|  | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     private fun parseStatus(status: String) = when { | ||||
|         status.contains("Ongoing") -> SManga.ONGOING | ||||
|         status.contains("Completed") -> SManga.COMPLETED | ||||
|         else -> SManga.UNKNOWN | ||||
|     } | ||||
|  | ||||
|     override fun chapterListSelector() = ".detail_list > ul:not([class]) > li" | ||||
|  | ||||
|     override fun chapterFromElement(element: Element): SChapter { | ||||
|         val parentEl = element.select("span.left").first() | ||||
|  | ||||
|         val urlElement = parentEl.select("a").first() | ||||
|  | ||||
|         var volume = parentEl.select("span.mr6")?.first()?.text()?.trim() ?: "" | ||||
|         if (volume.length > 0) { | ||||
|             volume = " - " + volume | ||||
|         } | ||||
|  | ||||
|         var title = parentEl?.textNodes()?.last()?.text()?.trim() ?: "" | ||||
|         if (title.length > 0) { | ||||
|             title = " - " + title | ||||
|         } | ||||
|  | ||||
|         val chapter = SChapter.create() | ||||
|         chapter.setUrlWithoutDomain(urlElement.attr("href")) | ||||
|         chapter.name = urlElement.text() + volume + title | ||||
|         chapter.date_upload = element.select("span.right").first()?.text()?.let { parseChapterDate(it) } ?: 0 | ||||
|         return chapter | ||||
|     } | ||||
|  | ||||
|     private fun parseChapterDate(date: String): Long { | ||||
|         return if ("Today" 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 licensedError = document.select(".mangaread_error > .mt10").first() | ||||
|         if (licensedError != null) { | ||||
|             throw Exception(licensedError.text()) | ||||
|         } | ||||
|  | ||||
|         val pages = mutableListOf<Page>() | ||||
|         document.select("select.wid60").first()?.getElementsByTag("option")?.forEach { | ||||
|             if (!it.attr("value").contains("featured.html")) { | ||||
|                 pages.add(Page(pages.size, "http:" + it.attr("value"))) | ||||
|             } | ||||
|         } | ||||
|         pages.getOrNull(0)?.imageUrl = imageUrlParse(document) | ||||
|         return pages | ||||
|     } | ||||
|  | ||||
|     override fun imageUrlParse(document: Document) = document.getElementById("image").attr("src") | ||||
|  | ||||
|     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 Type : Filter.Select<String>("Type", arrayOf("Any", "Japanese Manga (read from right to left)", "Korean Manhwa (read from left to right)")) | ||||
|     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()) | ||||
|     ) | ||||
|  | ||||
|     // [...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 | ||||
|     private fun getGenreList() = listOf( | ||||
|             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") | ||||
|     ) | ||||
|  | ||||
| } | ||||
| @@ -1,249 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.source.online.english | ||||
|  | ||||
| import eu.kanade.tachiyomi.network.POST | ||||
| import eu.kanade.tachiyomi.source.model.* | ||||
| import eu.kanade.tachiyomi.source.online.ParsedHttpSource | ||||
| import okhttp3.FormBody | ||||
| import okhttp3.Headers | ||||
| import okhttp3.HttpUrl | ||||
| import okhttp3.Request | ||||
| import org.jsoup.nodes.Document | ||||
| import org.jsoup.nodes.Element | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.regex.Pattern | ||||
|  | ||||
| class Mangasee : ParsedHttpSource() { | ||||
|  | ||||
|     override val id: Long = 9 | ||||
|  | ||||
|     override val name = "Mangasee" | ||||
|  | ||||
|     override val baseUrl = "http://mangaseeonline.us" | ||||
|  | ||||
|     override val lang = "en" | ||||
|  | ||||
|     override val supportsLatest = true | ||||
|  | ||||
|     private val recentUpdatesPattern = Pattern.compile("(.*?)\\s(\\d+\\.?\\d*)\\s?(Completed)?") | ||||
|  | ||||
|     private val indexPattern = Pattern.compile("-index-(.*?)-") | ||||
|  | ||||
|     private val catalogHeaders = Headers.Builder().apply { | ||||
|         add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") | ||||
|         add("Host", "mangaseeonline.us") | ||||
|     }.build() | ||||
|  | ||||
|     override fun popularMangaSelector() = "div.requested > div.row" | ||||
|  | ||||
|     override fun popularMangaRequest(page: Int): Request { | ||||
|         val (body, requestUrl) = convertQueryToPost(page, "$baseUrl/search/request.php?sortBy=popularity&sortOrder=descending") | ||||
|         return POST(requestUrl, catalogHeaders, body.build()) | ||||
|     } | ||||
|  | ||||
|     override fun popularMangaFromElement(element: Element): SManga { | ||||
|         val manga = SManga.create() | ||||
|         element.select("a.resultLink").first().let { | ||||
|             manga.setUrlWithoutDomain(it.attr("href")) | ||||
|             manga.title = it.text() | ||||
|         } | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     override fun popularMangaNextPageSelector() = "button.requestMore" | ||||
|  | ||||
|     override fun searchMangaSelector() = "div.requested > div.row" | ||||
|  | ||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { | ||||
|         val url = HttpUrl.parse("$baseUrl/search/request.php")!!.newBuilder() | ||||
|         if (!query.isEmpty()) url.addQueryParameter("keyword", query) | ||||
|         val genres = mutableListOf<String>() | ||||
|         val genresNo = mutableListOf<String>() | ||||
|         for (filter in if (filters.isEmpty()) getFilterList() else filters) { | ||||
|             when (filter) { | ||||
|                 is Sort -> { | ||||
|                     if (filter.state?.index != 0) | ||||
|                         url.addQueryParameter("sortBy", if (filter.state?.index == 1) "dateUpdated" else "popularity") | ||||
|                     if (filter.state?.ascending != true) | ||||
|                         url.addQueryParameter("sortOrder", "descending") | ||||
|                 } | ||||
|                 is SelectField -> 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 GenreList -> filter.state.forEach { genre -> | ||||
|                     when (genre.state) { | ||||
|                         Filter.TriState.STATE_INCLUDE -> genres.add(genre.name) | ||||
|                         Filter.TriState.STATE_EXCLUDE -> genresNo.add(genre.name) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (genres.isNotEmpty()) url.addQueryParameter("genre", genres.joinToString(",")) | ||||
|         if (genresNo.isNotEmpty()) url.addQueryParameter("genreNo", genresNo.joinToString(",")) | ||||
|  | ||||
|         val (body, requestUrl) = convertQueryToPost(page, url.toString()) | ||||
|         return POST(requestUrl, catalogHeaders, body.build()) | ||||
|     } | ||||
|  | ||||
|     private fun convertQueryToPost(page: Int, url: String): Pair<FormBody.Builder, String> { | ||||
|         val url = HttpUrl.parse(url)!! | ||||
|         val body = FormBody.Builder().add("page", page.toString()) | ||||
|         for (i in 0..url.querySize() - 1) { | ||||
|             body.add(url.queryParameterName(i), url.queryParameterValue(i)) | ||||
|         } | ||||
|         val requestUrl = url.scheme() + "://" + url.host() + url.encodedPath() | ||||
|         return Pair(body, requestUrl) | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaFromElement(element: Element): SManga { | ||||
|         val manga = SManga.create() | ||||
|         element.select("a.resultLink").first().let { | ||||
|             manga.setUrlWithoutDomain(it.attr("href")) | ||||
|             manga.title = it.text() | ||||
|         } | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaNextPageSelector() = "button.requestMore" | ||||
|  | ||||
|     override fun mangaDetailsParse(document: Document): SManga { | ||||
|         val detailElement = document.select("div.well > div.row").first() | ||||
|  | ||||
|         val manga = SManga.create() | ||||
|         manga.author = detailElement.select("a[href^=/search/?author=]").first()?.text() | ||||
|         manga.genre = detailElement.select("span.details > div.row > div:has(b:contains(Genre(s))) > a").map { it.text() }.joinToString() | ||||
|         manga.description = detailElement.select("strong:contains(Description:) + div").first()?.text() | ||||
|         manga.status = detailElement.select("a[href^=/search/?status=]").first()?.text().orEmpty().let { parseStatus(it) } | ||||
|         manga.thumbnail_url = detailElement.select("div > img").first()?.absUrl("src") | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     private fun parseStatus(status: String) = when { | ||||
|         status.contains("Ongoing (Scan)") -> SManga.ONGOING | ||||
|         status.contains("Complete (Scan)") -> SManga.COMPLETED | ||||
|         else -> SManga.UNKNOWN | ||||
|     } | ||||
|  | ||||
|     override fun chapterListSelector() = "div.chapter-list > a" | ||||
|  | ||||
|     override fun chapterFromElement(element: Element): SChapter { | ||||
|         val urlElement = element.select("a").first() | ||||
|  | ||||
|         val chapter = SChapter.create() | ||||
|         chapter.setUrlWithoutDomain(urlElement.attr("href")) | ||||
|         chapter.name = element.select("span.chapterLabel").first().text()?.let { it } ?: "" | ||||
|         chapter.date_upload = element.select("time").first()?.attr("datetime")?.let { parseChapterDate(it) } ?: 0 | ||||
|         return chapter | ||||
|     } | ||||
|  | ||||
|     private fun parseChapterDate(dateAsString: String): Long { | ||||
|         return SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(dateAsString).time | ||||
|     } | ||||
|  | ||||
|     override fun pageListParse(document: Document): List<Page> { | ||||
|         val fullUrl = document.baseUri() | ||||
|         val url = fullUrl.substringBeforeLast('/') | ||||
|  | ||||
|         val pages = mutableListOf<Page>() | ||||
|  | ||||
|         val series = document.select("input.IndexName").first().attr("value") | ||||
|         val chapter = document.select("span.CurChapter").first().text() | ||||
|         var index = "" | ||||
|  | ||||
|         val m = indexPattern.matcher(fullUrl) | ||||
|         if (m.find()) { | ||||
|             val indexNumber = m.group(1) | ||||
|             index = "-index-$indexNumber" | ||||
|         } | ||||
|  | ||||
|         document.select("div.ContainerNav").first().select("select.PageSelect > option").forEach { | ||||
|             pages.add(Page(pages.size, "$url/$series-chapter-$chapter$index-page-${pages.size + 1}.html")) | ||||
|         } | ||||
|         pages.getOrNull(0)?.imageUrl = imageUrlParse(document) | ||||
|         return pages | ||||
|     } | ||||
|  | ||||
|     override fun imageUrlParse(document: Document): String = document.select("img.CurImage").attr("src") | ||||
|  | ||||
|     override fun latestUpdatesNextPageSelector() = "button.requestMore" | ||||
|  | ||||
|     override fun latestUpdatesSelector(): String = "a.latestSeries" | ||||
|  | ||||
|     override fun latestUpdatesRequest(page: Int): Request { | ||||
|         val url = "http://mangaseeonline.net/home/latest.request.php" | ||||
|         val (body, requestUrl) = convertQueryToPost(page, url) | ||||
|         return POST(requestUrl, catalogHeaders, body.build()) | ||||
|     } | ||||
|  | ||||
|     override fun latestUpdatesFromElement(element: Element): SManga { | ||||
|         val manga = SManga.create() | ||||
|         element.select("a.latestSeries").first().let { | ||||
|             val chapterUrl = it.attr("href") | ||||
|             val indexOfMangaUrl = chapterUrl.indexOf("-chapter-") | ||||
|             val indexOfLastPath = chapterUrl.lastIndexOf("/") | ||||
|             val mangaUrl = chapterUrl.substring(indexOfLastPath, indexOfMangaUrl) | ||||
|             val defaultText = it.select("p.clamp2").text() | ||||
|             val m = recentUpdatesPattern.matcher(defaultText) | ||||
|             val title = if (m.matches()) m.group(1) else defaultText | ||||
|             manga.setUrlWithoutDomain("/manga" + mangaUrl) | ||||
|             manga.title = title | ||||
|         } | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     private class Sort : Filter.Sort("Sort", arrayOf("Alphabetically", "Date updated", "Popularity"), Filter.Sort.Selection(2, false)) | ||||
|     private class Genre(name: String) : Filter.TriState(name) | ||||
|     private class TextField(name: String, val key: String) : Filter.Text(name) | ||||
|     private class SelectField(name: String, val key: String, values: Array<String>, state: Int = 0) : Filter.Select<String>(name, values, state) | ||||
|     private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) | ||||
|  | ||||
|     override fun getFilterList() = FilterList( | ||||
|             TextField("Years", "year"), | ||||
|             TextField("Author", "author"), | ||||
|             SelectField("Scan Status", "status", arrayOf("Any", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing")), | ||||
|             SelectField("Publish Status", "pstatus", arrayOf("Any", "Cancelled", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing", "Unfinished")), | ||||
|             SelectField("Type", "type", arrayOf("Any", "Doujinshi", "Manga", "Manhua", "Manhwa", "OEL", "One-shot")), | ||||
|             Sort(), | ||||
|             GenreList(getGenreList()) | ||||
|     ) | ||||
|  | ||||
|     // [...document.querySelectorAll("label.triStateCheckBox input")].map(el => `Filter("${el.getAttribute('name')}", "${el.nextSibling.textContent.trim()}")`).join(',\n') | ||||
|     // http://mangasee.co/advanced-search/ | ||||
|     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("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") | ||||
|     ) | ||||
|  | ||||
| } | ||||
| @@ -1,224 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.source.online.english | ||||
|  | ||||
| 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 okhttp3.Headers | ||||
| import okhttp3.OkHttpClient | ||||
| import okhttp3.Request | ||||
| import org.jsoup.nodes.Document | ||||
| import org.jsoup.nodes.Element | ||||
| import java.util.* | ||||
|  | ||||
| class Readmangatoday : ParsedHttpSource() { | ||||
|  | ||||
|     override val id: Long = 8 | ||||
|  | ||||
|     override val name = "ReadMangaToday" | ||||
|  | ||||
|     override val baseUrl = "https://www.readmng.com" | ||||
|  | ||||
|     override val lang = "en" | ||||
|  | ||||
|     override val supportsLatest = true | ||||
|  | ||||
|     override val client: OkHttpClient get() = network.cloudflareClient | ||||
|  | ||||
|     /** | ||||
|      * Search only returns data with this set | ||||
|      */ | ||||
|     override fun headersBuilder() = Headers.Builder().apply { | ||||
|         add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") | ||||
|         add("X-Requested-With", "XMLHttpRequest") | ||||
|     } | ||||
|  | ||||
|     override fun popularMangaRequest(page: Int): Request { | ||||
|         return GET("$baseUrl/hot-manga/$page", headers) | ||||
|     } | ||||
|  | ||||
|     override fun latestUpdatesRequest(page: Int): Request { | ||||
|         return GET("$baseUrl/latest-releases/$page", headers) | ||||
|     } | ||||
|  | ||||
|     override fun popularMangaSelector() = "div.hot-manga > div.style-list > div.box" | ||||
|  | ||||
|     override fun latestUpdatesSelector() = "div.hot-manga > div.style-grid > div.box" | ||||
|  | ||||
|     override fun popularMangaFromElement(element: Element): SManga { | ||||
|         val manga = SManga.create() | ||||
|         element.select("div.title > h2 > a").first().let { | ||||
|             manga.setUrlWithoutDomain(it.attr("href")) | ||||
|             manga.title = it.attr("title") | ||||
|         } | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     override fun latestUpdatesFromElement(element: Element): SManga { | ||||
|         return popularMangaFromElement(element) | ||||
|     } | ||||
|  | ||||
|     override fun popularMangaNextPageSelector() = "div.hot-manga > ul.pagination > li > a:contains(»)" | ||||
|  | ||||
|     override fun latestUpdatesNextPageSelector() = "div.hot-manga > ul.pagination > li > a:contains(»)" | ||||
|  | ||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { | ||||
|         val builder = okhttp3.FormBody.Builder() | ||||
|         builder.add("manga-name", query) | ||||
|         (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> | ||||
|             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 GenreList -> filter.state.forEach { genre -> | ||||
|                     when (genre.state) { | ||||
|                         Filter.TriState.STATE_INCLUDE -> builder.add("include[]", genre.id.toString()) | ||||
|                         Filter.TriState.STATE_EXCLUDE -> builder.add("exclude[]", genre.id.toString()) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return POST("$baseUrl/service/advanced_search", headers, builder.build()) | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaSelector() = "div.style-list > div.box" | ||||
|  | ||||
|     override fun searchMangaFromElement(element: Element): SManga { | ||||
|         val manga = SManga.create() | ||||
|         element.select("div.title > h2 > a").first().let { | ||||
|             manga.setUrlWithoutDomain(it.attr("href")) | ||||
|             manga.title = it.attr("title") | ||||
|         } | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaNextPageSelector() = "div.next-page > a.next" | ||||
|  | ||||
|     override fun mangaDetailsParse(document: Document): SManga { | ||||
|         val detailElement = document.select("div.movie-meta").first() | ||||
|         val genreElement = detailElement.select("dl.dl-horizontal > dd:eq(5) a") | ||||
|  | ||||
|         val manga = SManga.create() | ||||
|         manga.author = document.select("ul.cast-list li.director > ul a").first()?.text() | ||||
|         manga.artist = document.select("ul.cast-list li:not(.director) > ul a").first()?.text() | ||||
|         manga.description = detailElement.select("li.movie-detail").first()?.text() | ||||
|         manga.status = detailElement.select("dl.dl-horizontal > dd:eq(3)").first()?.text().orEmpty().let { parseStatus(it) } | ||||
|         manga.thumbnail_url = detailElement.select("img.img-responsive").first()?.attr("src") | ||||
|  | ||||
|         var genres = mutableListOf<String>() | ||||
|         genreElement?.forEach { genres.add(it.text()) } | ||||
|         manga.genre = genres.joinToString(", ") | ||||
|  | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     private fun parseStatus(status: String) = when { | ||||
|         status.contains("Ongoing") -> SManga.ONGOING | ||||
|         status.contains("Completed") -> SManga.COMPLETED | ||||
|         else -> SManga.UNKNOWN | ||||
|     } | ||||
|  | ||||
|     override fun chapterListSelector() = "ul.chp_lst > li" | ||||
|  | ||||
|     override fun chapterFromElement(element: Element): SChapter { | ||||
|         val urlElement = element.select("a").first() | ||||
|  | ||||
|         val chapter = SChapter.create() | ||||
|         chapter.setUrlWithoutDomain(urlElement.attr("href")) | ||||
|         chapter.name = urlElement.select("span.val").text() | ||||
|         chapter.date_upload = element.select("span.dte").first()?.text()?.let { parseChapterDate(it) } ?: 0 | ||||
|         return chapter | ||||
|     } | ||||
|  | ||||
|     private fun parseChapterDate(date: String): Long { | ||||
|         val dateWords: List<String> = date.split(" ") | ||||
|  | ||||
|         if (dateWords.size == 3) { | ||||
|             val timeAgo = Integer.parseInt(dateWords[0]) | ||||
|             val date: Calendar = Calendar.getInstance() | ||||
|  | ||||
|             if (dateWords[1].contains("Minute")) { | ||||
|                 date.add(Calendar.MINUTE, -timeAgo) | ||||
|             } else if (dateWords[1].contains("Hour")) { | ||||
|                 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")) { | ||||
|                 date.add(Calendar.WEEK_OF_YEAR, -timeAgo) | ||||
|             } else if (dateWords[1].contains("Month")) { | ||||
|                 date.add(Calendar.MONTH, -timeAgo) | ||||
|             } else if (dateWords[1].contains("Year")) { | ||||
|                 date.add(Calendar.YEAR, -timeAgo) | ||||
|             } | ||||
|  | ||||
|             return date.timeInMillis | ||||
|         } | ||||
|  | ||||
|         return 0L | ||||
|     } | ||||
|  | ||||
|     override fun pageListParse(document: Document): List<Page> { | ||||
|         val pages = mutableListOf<Page>() | ||||
|         document.select("ul.list-switcher-2 > li > select.jump-menu").first().getElementsByTag("option").forEach { | ||||
|             pages.add(Page(pages.size, it.attr("value"))) | ||||
|         } | ||||
|         pages.getOrNull(0)?.imageUrl = imageUrlParse(document) | ||||
|         return pages | ||||
|     } | ||||
|  | ||||
|     override fun imageUrlParse(document: Document) = document.select("#chapter_img").first().attr("src") | ||||
|  | ||||
|     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.Select<String>("Type", arrayOf("All", "Japanese Manga", "Korean Manhwa", "Chinese Manhua")) | ||||
|     private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) | ||||
|  | ||||
|     override fun getFilterList() = FilterList( | ||||
|             TextField("Author", "author-name"), | ||||
|             TextField("Artist", "artist-name"), | ||||
|             Type(), | ||||
|             Status(), | ||||
|             GenreList(getGenreList()) | ||||
|     ) | ||||
|  | ||||
|     // [...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 | ||||
|     private fun getGenreList() = listOf( | ||||
|             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) | ||||
|     ) | ||||
| } | ||||
| @@ -1,122 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.source.online.german | ||||
|  | ||||
| import eu.kanade.tachiyomi.network.GET | ||||
| import eu.kanade.tachiyomi.source.model.FilterList | ||||
| import eu.kanade.tachiyomi.source.model.Page | ||||
| import eu.kanade.tachiyomi.source.model.SChapter | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import eu.kanade.tachiyomi.source.online.ParsedHttpSource | ||||
| import okhttp3.Request | ||||
| import org.jsoup.nodes.Document | ||||
| import org.jsoup.nodes.Element | ||||
| import java.text.SimpleDateFormat | ||||
|  | ||||
| class WieManga : ParsedHttpSource() { | ||||
|  | ||||
|     override val id: Long = 10 | ||||
|  | ||||
|     override val name = "Wie Manga!" | ||||
|  | ||||
|     override val baseUrl = "http://www.wiemanga.com" | ||||
|  | ||||
|     override val lang = "de" | ||||
|  | ||||
|     override val supportsLatest = true | ||||
|  | ||||
|     override fun popularMangaSelector() = ".booklist td > div" | ||||
|  | ||||
|     override fun latestUpdatesSelector() = ".booklist td > div" | ||||
|  | ||||
|     override fun popularMangaRequest(page: Int): Request { | ||||
|         return GET("$baseUrl/list/Hot-Book/", headers) | ||||
|     } | ||||
|  | ||||
|     override fun latestUpdatesRequest(page: Int): Request { | ||||
|         return GET("$baseUrl/list/New-Update/", headers) | ||||
|     } | ||||
|  | ||||
|     override fun popularMangaFromElement(element: Element): SManga { | ||||
|         val image = element.select("dt img") | ||||
|         val title = element.select("dd a:first-child") | ||||
|  | ||||
|         val manga = SManga.create() | ||||
|         manga.setUrlWithoutDomain(title.attr("href")) | ||||
|         manga.title = title.text() | ||||
|         manga.thumbnail_url = image.attr("src") | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     override fun latestUpdatesFromElement(element: Element): SManga { | ||||
|         return popularMangaFromElement(element) | ||||
|     } | ||||
|  | ||||
|     override fun popularMangaNextPageSelector() = null | ||||
|  | ||||
|     override fun latestUpdatesNextPageSelector() = null | ||||
|  | ||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { | ||||
|         return GET("$baseUrl/search/?wd=$query", headers) | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaSelector() = ".searchresult td > div" | ||||
|  | ||||
|     override fun searchMangaFromElement(element: Element): SManga { | ||||
|         val image = element.select(".resultimg img") | ||||
|         val title = element.select(".resultbookname") | ||||
|  | ||||
|         val manga = SManga.create() | ||||
|         manga.setUrlWithoutDomain(title.attr("href")) | ||||
|         manga.title = title.text() | ||||
|         manga.thumbnail_url = image.attr("src") | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaNextPageSelector() = ".pagetor a.l" | ||||
|  | ||||
|     override fun mangaDetailsParse(document: Document): SManga { | ||||
|         val imageElement = document.select(".bookmessgae tr > td:nth-child(1)").first() | ||||
|         val infoElement = document.select(".bookmessgae tr > td:nth-child(2)").first() | ||||
|  | ||||
|         val manga = SManga.create() | ||||
|         manga.author = infoElement.select("dd:nth-of-type(2) a").first()?.text() | ||||
|         manga.artist = infoElement.select("dd:nth-of-type(3) a").first()?.text() | ||||
|         manga.description = infoElement.select("dl > dt:last-child").first()?.text()?.replaceFirst("Beschreibung", "") | ||||
|         manga.thumbnail_url = imageElement.select("img").first()?.attr("src") | ||||
|  | ||||
|         if (manga.author == "RSS") | ||||
|             manga.author = null | ||||
|  | ||||
|         if (manga.artist == "RSS") | ||||
|             manga.artist = null | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     override fun chapterListSelector() = ".chapterlist tr:not(:first-child)" | ||||
|  | ||||
|     override fun chapterFromElement(element: Element): SChapter { | ||||
|         val urlElement = element.select(".col1 a").first() | ||||
|         val dateElement = element.select(".col3 a").first() | ||||
|  | ||||
|         val chapter = SChapter.create() | ||||
|         chapter.setUrlWithoutDomain(urlElement.attr("href")) | ||||
|         chapter.name = urlElement.text() | ||||
|         chapter.date_upload = dateElement?.text()?.let { parseChapterDate(it) } ?: 0 | ||||
|         return chapter | ||||
|     } | ||||
|  | ||||
|     private fun parseChapterDate(date: String): Long { | ||||
|         return SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(date).time | ||||
|     } | ||||
|  | ||||
|     override fun pageListParse(document: Document): List<Page> { | ||||
|         val pages = mutableListOf<Page>() | ||||
|  | ||||
|         document.select("select#page").first().select("option").forEach { | ||||
|             pages.add(Page(pages.size, it.attr("value"))) | ||||
|         } | ||||
|         return pages | ||||
|     } | ||||
|  | ||||
|     override fun imageUrlParse(document: Document) = document.select("img#comicpic").first().attr("src") | ||||
|  | ||||
| } | ||||
| @@ -1,290 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.source.online.russian | ||||
|  | ||||
| import eu.kanade.tachiyomi.network.GET | ||||
| import eu.kanade.tachiyomi.source.model.* | ||||
| import eu.kanade.tachiyomi.source.online.ParsedHttpSource | ||||
| import eu.kanade.tachiyomi.util.asJsoup | ||||
| import okhttp3.Request | ||||
| import okhttp3.Response | ||||
| import org.jsoup.nodes.Document | ||||
| import org.jsoup.nodes.Element | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.* | ||||
|  | ||||
| class Mangachan : ParsedHttpSource() { | ||||
|  | ||||
|     override val id: Long = 7 | ||||
|  | ||||
|     override val name = "Mangachan" | ||||
|  | ||||
|     override val baseUrl = "http://mangachan.me" | ||||
|  | ||||
|     override val lang = "ru" | ||||
|  | ||||
|     override val supportsLatest = true | ||||
|  | ||||
|     override fun popularMangaRequest(page: Int): Request = | ||||
|             GET("$baseUrl/mostfavorites?offset=${20 * (page - 1)}", headers) | ||||
|  | ||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { | ||||
|         var pageNum = 1 | ||||
|         when { | ||||
|             page <  1 -> pageNum = 1 | ||||
|             page >= 1 -> pageNum = page | ||||
|         } | ||||
|         val url = if (query.isNotEmpty()) { | ||||
|             "$baseUrl/?do=search&subaction=search&story=$query&search_start=$pageNum" | ||||
|         } else { | ||||
|  | ||||
|             var genres = "" | ||||
|             var order = "" | ||||
|             var statusParam = true | ||||
|             var status = "" | ||||
|             for (filter in if (filters.isEmpty()) getFilterList() else filters) { | ||||
|                 when (filter) { | ||||
|                     is GenreList -> { | ||||
|                         filter.state.forEach { f -> | ||||
|                             if (!f.isIgnored()) { | ||||
|                                 genres += (if (f.isExcluded()) "-" else "") + f.id + '+' | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     is OrderBy -> { | ||||
|                         if (filter.state!!.ascending && filter.state!!.index == 0) { | ||||
|                             statusParam = false | ||||
|                         } | ||||
|                     } | ||||
|                     is Status -> status = arrayOf("", "all_done", "end", "ongoing", "new_ch")[filter.state] | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (genres.isNotEmpty()) { | ||||
|                 for (filter in filters) { | ||||
|                     when (filter) { | ||||
|                         is OrderBy -> { | ||||
|                             order = if (filter.state!!.ascending) { | ||||
|                                 arrayOf("", "&n=favasc", "&n=abcdesc", "&n=chasc")[filter.state!!.index] | ||||
|                             } else { | ||||
|                                 arrayOf("&n=dateasc", "&n=favdesc", "&n=abcasc", "&n=chdesc")[filter.state!!.index] | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 if (statusParam) { | ||||
|                     "$baseUrl/tags/${genres.dropLast(1)}$order?offset=${20 * (pageNum - 1)}&status=$status" | ||||
|                 } else { | ||||
|                     "$baseUrl/tags/$status/${genres.dropLast(1)}/$order?offset=${20 * (pageNum - 1)}" | ||||
|                 } | ||||
|             } else { | ||||
|                 for (filter in filters) { | ||||
|                     when (filter) { | ||||
|                         is OrderBy -> { | ||||
|                             order = if (filter.state!!.ascending) { | ||||
|                                 arrayOf("manga/new", "manga/new&n=favasc", "manga/new&n=abcdesc", "manga/new&n=chasc")[filter.state!!.index] | ||||
|                             } else { | ||||
|                                 arrayOf("manga/new&n=dateasc", "mostfavorites", "catalog", "sortch")[filter.state!!.index] | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 if (statusParam) { | ||||
|                     "$baseUrl/$order?offset=${20 * (pageNum - 1)}&status=$status" | ||||
|                 } else { | ||||
|                     "$baseUrl/$order/$status?offset=${20 * (pageNum - 1)}" | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return GET(url, headers) | ||||
|     } | ||||
|  | ||||
|     override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/newestch?page=$page") | ||||
|  | ||||
|     override fun popularMangaSelector() = "div.content_row" | ||||
|  | ||||
|     override fun latestUpdatesSelector() = "ul.area_rightNews li" | ||||
|  | ||||
|     override fun searchMangaSelector() = popularMangaSelector() | ||||
|  | ||||
|     override fun popularMangaFromElement(element: Element): SManga { | ||||
|         val manga = SManga.create() | ||||
|         manga.thumbnail_url = element.select("div.manga_images img").first().attr("src") | ||||
|         element.select("h2 > a").first().let { | ||||
|             manga.setUrlWithoutDomain(it.attr("href")) | ||||
|             manga.title = it.text() | ||||
|         } | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     override fun latestUpdatesFromElement(element: Element): SManga { | ||||
|         val manga = SManga.create() | ||||
|         element.select("a:nth-child(1)").first().let { | ||||
|             manga.setUrlWithoutDomain(it.attr("href")) | ||||
|             manga.title = it.text() | ||||
|         } | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) | ||||
|  | ||||
|     override fun popularMangaNextPageSelector() = "a:contains(Вперед)" | ||||
|  | ||||
|     override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() | ||||
|  | ||||
|     override fun searchMangaNextPageSelector() = "a:contains(Далее)" | ||||
|  | ||||
|     private fun searchGenresNextPageSelector() = popularMangaNextPageSelector() | ||||
|  | ||||
|     override fun searchMangaParse(response: Response): MangasPage { | ||||
|         val document = response.asJsoup() | ||||
|         var hasNextPage = false | ||||
|  | ||||
|         val mangas = document.select(searchMangaSelector()).map { element -> | ||||
|             searchMangaFromElement(element) | ||||
|         } | ||||
|  | ||||
|         val nextSearchPage = document.select(searchMangaNextPageSelector()) | ||||
|         if (nextSearchPage.isNotEmpty()) { | ||||
|             val query = document.select("input#searchinput").first().attr("value") | ||||
|             val pageNum = nextSearchPage.let { selector -> | ||||
|                 val onClick = selector.attr("onclick") | ||||
|                 onClick?.split("""\\d+""") | ||||
|             } | ||||
|             nextSearchPage.attr("href", "$baseUrl/?do=search&subaction=search&story=$query&search_start=$pageNum") | ||||
|             hasNextPage = true | ||||
|         } | ||||
|  | ||||
|         val nextGenresPage = document.select(searchGenresNextPageSelector()) | ||||
|         if (nextGenresPage.isNotEmpty()) { | ||||
|             hasNextPage = true | ||||
|         } | ||||
|  | ||||
|         return MangasPage(mangas, hasNextPage) | ||||
|     } | ||||
|  | ||||
|     override fun mangaDetailsParse(document: Document): SManga { | ||||
|         val infoElement = document.select("table.mangatitle").first() | ||||
|         val descElement = document.select("div#description").first() | ||||
|         val imgElement = document.select("img#cover").first() | ||||
|  | ||||
|         val manga = SManga.create() | ||||
|         manga.author = infoElement.select("tr:eq(2) > td:eq(1)").text() | ||||
|         manga.genre = infoElement.select("tr:eq(5) > td:eq(1)").text() | ||||
|         manga.status = parseStatus(infoElement.select("tr:eq(4) > td:eq(1)").text()) | ||||
|         manga.description = descElement.textNodes().first().text() | ||||
|         manga.thumbnail_url = imgElement.attr("src") | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     private fun parseStatus(element: String): Int = when { | ||||
|         element.contains("перевод завершен") -> SManga.COMPLETED | ||||
|         element.contains("перевод продолжается") -> SManga.ONGOING | ||||
|         else -> SManga.UNKNOWN | ||||
|     } | ||||
|  | ||||
|     override fun chapterListSelector() = "table.table_cha tr:gt(1)" | ||||
|  | ||||
|     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("div.date").first()?.text()?.let { | ||||
|             SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(it).time | ||||
|         } ?: 0 | ||||
|         return chapter | ||||
|     } | ||||
|  | ||||
|     override fun pageListParse(response: Response): List<Page> { | ||||
|         val html = response.body()!!.string() | ||||
|         val beginIndex = html.indexOf("fullimg\":[") + 10 | ||||
|         val endIndex = html.indexOf(",]", beginIndex) | ||||
|         val trimmedHtml = html.substring(beginIndex, endIndex).replace("\"", "") | ||||
|         val pageUrls = trimmedHtml.split(',') | ||||
|  | ||||
|         return pageUrls.mapIndexed { i, url -> Page(i, "", url) } | ||||
|     } | ||||
|  | ||||
|     override fun pageListParse(document: Document): List<Page> { | ||||
|         throw Exception("Not used") | ||||
|     } | ||||
|  | ||||
|     override fun imageUrlParse(document: Document) = "" | ||||
|  | ||||
|     private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Тэги", genres) | ||||
|     private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name) | ||||
|     private class Status : Filter.Select<String>("Статус", arrayOf("Все", "Перевод завершен", "Выпуск завершен", "Онгоинг", "Новые главы")) | ||||
|     private class OrderBy : Filter.Sort("Сортировка", | ||||
|             arrayOf("Дата", "Популярность", "Имя", "Главы"), | ||||
|             Filter.Sort.Selection(1, false)) | ||||
|  | ||||
|  | ||||
|     override fun getFilterList() = FilterList( | ||||
|             Status(), | ||||
|             OrderBy(), | ||||
|             GenreList(getGenreList()) | ||||
|     ) | ||||
|  | ||||
|  | ||||
|     /* [...document.querySelectorAll("li.sidetag > a:nth-child(1)")] | ||||
|     *  .map(el => `Genre("${el.getAttribute('href').substr(6)}")`).join(',\n') | ||||
|     *  on http://mangachan.me/ | ||||
|     */ | ||||
|     private fun getGenreList() = 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("ёнкома") | ||||
|     ) | ||||
| } | ||||
| @@ -1,251 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.source.online.russian | ||||
|  | ||||
| import eu.kanade.tachiyomi.network.GET | ||||
| import eu.kanade.tachiyomi.source.model.* | ||||
| import eu.kanade.tachiyomi.source.online.ParsedHttpSource | ||||
| import okhttp3.Headers | ||||
| import okhttp3.HttpUrl | ||||
| import okhttp3.Request | ||||
| import okhttp3.Response | ||||
| import org.jsoup.nodes.Document | ||||
| import org.jsoup.nodes.Element | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.* | ||||
| import java.util.regex.Pattern | ||||
|  | ||||
| class Mintmanga : ParsedHttpSource() { | ||||
|  | ||||
|     override val id: Long = 6 | ||||
|  | ||||
|     override val name = "Mintmanga" | ||||
|  | ||||
|     override val baseUrl = "http://mintmanga.com" | ||||
|  | ||||
|     override val lang = "ru" | ||||
|  | ||||
|     override val supportsLatest = true | ||||
|  | ||||
|     override fun popularMangaRequest(page: Int): Request = | ||||
|             GET("$baseUrl/list?sortType=rate&offset=${70 * (page - 1)}&max=70", headers) | ||||
|  | ||||
|     override fun latestUpdatesRequest(page: Int): Request = | ||||
|             GET("$baseUrl/list?sortType=updated&offset=${70 * (page - 1)}&max=70", headers) | ||||
|  | ||||
|     override fun popularMangaSelector() = "div.tile" | ||||
|  | ||||
|     override fun latestUpdatesSelector() = "div.tile" | ||||
|  | ||||
|     override fun popularMangaFromElement(element: Element): SManga { | ||||
|         val manga = SManga.create() | ||||
|         manga.thumbnail_url = element.select("img.lazy").first()?.attr("data-original") | ||||
|         element.select("h3 > a").first().let { | ||||
|             manga.setUrlWithoutDomain(it.attr("href")) | ||||
|             manga.title = it.attr("title") | ||||
|         } | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     override fun latestUpdatesFromElement(element: Element): SManga = | ||||
|             popularMangaFromElement(element) | ||||
|  | ||||
|     override fun popularMangaNextPageSelector() = "a.nextLink" | ||||
|  | ||||
|     override fun latestUpdatesNextPageSelector() = "a.nextLink" | ||||
|  | ||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { | ||||
|         val url = HttpUrl.parse("$baseUrl/search/advanced")!!.newBuilder() | ||||
|         (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> | ||||
|             when (filter) { | ||||
|                 is GenreList -> filter.state.forEach { genre -> | ||||
|                     if (genre.state != Filter.TriState.STATE_IGNORE) { | ||||
|                         url.addQueryParameter(genre.id, arrayOf("=", "=in", "=ex")[genre.state]) | ||||
|                     } | ||||
|                 } | ||||
|                 is Category -> filter.state.forEach { category -> | ||||
|                     if (category.state != Filter.TriState.STATE_IGNORE) { | ||||
|                         url.addQueryParameter(category.id, arrayOf("=", "=in", "=ex")[category.state]) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (!query.isEmpty()) { | ||||
|             url.addQueryParameter("q", query) | ||||
|         } | ||||
|         return GET(url.toString().replace("=%3D", "="), headers) | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaSelector() = popularMangaSelector() | ||||
|  | ||||
|     override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) | ||||
|  | ||||
|     // max 200 results | ||||
|     override fun searchMangaNextPageSelector(): Nothing? = null | ||||
|  | ||||
|     override fun mangaDetailsParse(document: Document): SManga { | ||||
|         val infoElement = document.select("div.leftContent").first() | ||||
|  | ||||
|         val manga = SManga.create() | ||||
|         manga.author = infoElement.select("span.elem_author").first()?.text() | ||||
|         manga.genre = infoElement.select("span.elem_genre").text().replace(" ,", ",") | ||||
|         manga.description = infoElement.select("div.manga-description").text() | ||||
|         manga.status = parseStatus(infoElement.html()) | ||||
|         manga.thumbnail_url = infoElement.select("img").attr("data-full") | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     private fun parseStatus(element: String): Int = when { | ||||
|         element.contains("<h3>Запрещена публикация произведения по копирайту</h3>") -> SManga.LICENSED | ||||
|         element.contains("<h1 class=\"names\"> Сингл") || element.contains("<b>Перевод:</b> завершен") -> SManga.COMPLETED | ||||
|         element.contains("<b>Перевод:</b> продолжается") -> SManga.ONGOING | ||||
|         else -> SManga.UNKNOWN | ||||
|     } | ||||
|  | ||||
|     override fun chapterListSelector() = "div.chapters-link tbody tr" | ||||
|  | ||||
|     override fun chapterFromElement(element: Element): SChapter { | ||||
|         val urlElement = element.select("a").first() | ||||
|         val urlText = urlElement.text() | ||||
|  | ||||
|         val chapter = SChapter.create() | ||||
|         chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mtr=1") | ||||
|         if (urlText.endsWith(" новое")) { | ||||
|             chapter.name = urlText.dropLast(6) | ||||
|         } else { | ||||
|             chapter.name = urlText | ||||
|         } | ||||
|         chapter.date_upload = element.select("td.hidden-xxs").last()?.text()?.let { | ||||
|             SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time | ||||
|         } ?: 0 | ||||
|         return chapter | ||||
|     } | ||||
|  | ||||
|     override fun prepareNewChapter(chapter: SChapter, manga: SManga) { | ||||
|         val basic = Regex("""\s*([0-9]+)(\s-\s)([0-9]+)\s*""") | ||||
|         val extra = Regex("""\s*([0-9]+\sЭкстра)\s*""") | ||||
|         val single = Regex("""\s*Сингл\s*""") | ||||
|         when { | ||||
|             basic.containsMatchIn(chapter.name) -> { | ||||
|                 basic.find(chapter.name)?.let { | ||||
|                     val number = it.groups[3]?.value!! | ||||
|                     chapter.chapter_number = number.toFloat() | ||||
|                 } | ||||
|             } | ||||
|             extra.containsMatchIn(chapter.name) -> // Extra chapters doesn't contain chapter number | ||||
|                 chapter.chapter_number = -2f | ||||
|             single.containsMatchIn(chapter.name) -> // Oneshoots, doujinshi and other mangas with one chapter | ||||
|                 chapter.chapter_number = 1f | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun pageListParse(response: Response): List<Page> { | ||||
|         val html = response.body()!!.string() | ||||
|         val beginIndex = html.indexOf("rm_h.init( [") | ||||
|         val endIndex = html.indexOf("], 0, false);", beginIndex) | ||||
|         val trimmedHtml = html.substring(beginIndex, endIndex) | ||||
|  | ||||
|         val p = Pattern.compile("'.*?','.*?',\".*?\"") | ||||
|         val m = p.matcher(trimmedHtml) | ||||
|  | ||||
|         val pages = mutableListOf<Page>() | ||||
|  | ||||
|         var i = 0 | ||||
|         while (m.find()) { | ||||
|             val urlParts = m.group().replace("[\"\']+".toRegex(), "").split(',') | ||||
|             val url = if (urlParts[1].isEmpty() && urlParts[2].startsWith("/static/")) { | ||||
|                 baseUrl + urlParts[2] | ||||
|             } else { | ||||
|                 urlParts[1] + urlParts[0] + urlParts[2] | ||||
|             } | ||||
|             pages.add(Page(i++, "", url)) | ||||
|         } | ||||
|         return pages | ||||
|     } | ||||
|  | ||||
|     override fun pageListParse(document: Document): List<Page> { | ||||
|         throw Exception("Not used") | ||||
|     } | ||||
|  | ||||
|     override fun imageUrlParse(document: Document) = "" | ||||
|  | ||||
|     override fun imageRequest(page: Page): Request { | ||||
|         val imgHeader = Headers.Builder().apply { | ||||
|             add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") | ||||
|             add("Referer", baseUrl) | ||||
|         }.build() | ||||
|         return GET(page.imageUrl!!, imgHeader) | ||||
|     } | ||||
|  | ||||
|     private class Genre(name: String, val id: String) : Filter.TriState(name) | ||||
|     private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) | ||||
|     private class Category(categories: List<Genre>) : Filter.Group<Genre>("Category", categories) | ||||
|  | ||||
|     /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")] | ||||
|     *  .map(el => `Genre("${el.textContent.trim()}", "${el.getAttribute('onclick') | ||||
|     *  .substr(31,el.getAttribute('onclick').length-33)"})`).join(',\n') | ||||
|     *  on http://mintmanga.com/search/advanced | ||||
|     */ | ||||
|     override fun getFilterList() = FilterList( | ||||
|             Category(getCategoryList()), | ||||
|             GenreList(getGenreList()) | ||||
|     ) | ||||
|  | ||||
|     private fun getCategoryList() = listOf( | ||||
|             Genre("В цвете", "el_4614"), | ||||
|             Genre("Веб", "el_1355"), | ||||
|             Genre("Выпуск приостановлен", "el_5232"), | ||||
|             Genre("Ёнкома", "el_2741"), | ||||
|             Genre("Комикс западный", "el_1903"), | ||||
|             Genre("Комикс русский", "el_2173"), | ||||
|             Genre("Манхва", "el_1873"), | ||||
|             Genre("Маньхуа", "el_1875"), | ||||
|             Genre("Не Яой", "el_1874"), | ||||
|             Genre("Ранобэ", "el_5688"), | ||||
|             Genre("Сборник", "el_1348") | ||||
|     ) | ||||
|  | ||||
|     private fun getGenreList() = 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_5676"), | ||||
|             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") | ||||
|     ) | ||||
| } | ||||
| @@ -1,247 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.source.online.russian | ||||
|  | ||||
| import eu.kanade.tachiyomi.network.GET | ||||
| import eu.kanade.tachiyomi.source.model.* | ||||
| import eu.kanade.tachiyomi.source.online.ParsedHttpSource | ||||
| import okhttp3.Headers | ||||
| import okhttp3.HttpUrl | ||||
| import okhttp3.Request | ||||
| import okhttp3.Response | ||||
| import org.jsoup.nodes.Document | ||||
| import org.jsoup.nodes.Element | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.* | ||||
| import java.util.regex.Pattern | ||||
|  | ||||
| class Readmanga : ParsedHttpSource() { | ||||
|  | ||||
|     override val id: Long = 5 | ||||
|  | ||||
|     override val name = "Readmanga" | ||||
|  | ||||
|     override val baseUrl = "http://readmanga.me" | ||||
|  | ||||
|     override val lang = "ru" | ||||
|  | ||||
|     override val supportsLatest = true | ||||
|  | ||||
|     override fun popularMangaSelector() = "div.tile" | ||||
|  | ||||
|     override fun latestUpdatesSelector() = "div.tile" | ||||
|  | ||||
|     override fun popularMangaRequest(page: Int): Request = | ||||
|             GET("$baseUrl/list?sortType=rate&offset=${70 * (page - 1)}&max=70", headers) | ||||
|  | ||||
|     override fun latestUpdatesRequest(page: Int): Request = | ||||
|             GET("$baseUrl/list?sortType=updated&offset=${70 * (page - 1)}&max=70", headers) | ||||
|  | ||||
|     override fun popularMangaFromElement(element: Element): SManga { | ||||
|         val manga = SManga.create() | ||||
|         manga.thumbnail_url = element.select("img.lazy").first()?.attr("data-original") | ||||
|         element.select("h3 > a").first().let { | ||||
|             manga.setUrlWithoutDomain(it.attr("href")) | ||||
|             manga.title = it.attr("title") | ||||
|         } | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     override fun latestUpdatesFromElement(element: Element): SManga = | ||||
|             popularMangaFromElement(element) | ||||
|  | ||||
|     override fun popularMangaNextPageSelector() = "a.nextLink" | ||||
|  | ||||
|     override fun latestUpdatesNextPageSelector() = "a.nextLink" | ||||
|  | ||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { | ||||
|         val url = HttpUrl.parse("$baseUrl/search/advanced")!!.newBuilder() | ||||
|         (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> | ||||
|             when (filter) { | ||||
|                 is GenreList -> filter.state.forEach { genre -> | ||||
|                     if (genre.state != Filter.TriState.STATE_IGNORE) { | ||||
|                         url.addQueryParameter(genre.id, arrayOf("=", "=in", "=ex")[genre.state]) | ||||
|                     } | ||||
|                 } | ||||
|                 is Category -> filter.state.forEach { category -> | ||||
|                     if (category.state != Filter.TriState.STATE_IGNORE) { | ||||
|                         url.addQueryParameter(category.id, arrayOf("=", "=in", "=ex")[category.state]) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (!query.isEmpty()) { | ||||
|             url.addQueryParameter("q", query) | ||||
|         } | ||||
|         return GET(url.toString().replace("=%3D", "="), headers) | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaSelector() = popularMangaSelector() | ||||
|  | ||||
|     override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) | ||||
|  | ||||
|     // max 200 results | ||||
|     override fun searchMangaNextPageSelector(): Nothing? = null | ||||
|  | ||||
|     override fun mangaDetailsParse(document: Document): SManga { | ||||
|         val infoElement = document.select("div.leftContent").first() | ||||
|  | ||||
|         val manga = SManga.create() | ||||
|         manga.author = infoElement.select("span.elem_author").first()?.text() | ||||
|         manga.genre = infoElement.select("span.elem_genre").text().replace(" ,", ",") | ||||
|         manga.description = infoElement.select("div.manga-description").text() | ||||
|         manga.status = parseStatus(infoElement.html()) | ||||
|         manga.thumbnail_url = infoElement.select("img").attr("data-full") | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     private fun parseStatus(element: String): Int = when { | ||||
|         element.contains("<h3>Запрещена публикация произведения по копирайту</h3>") -> SManga.LICENSED | ||||
|         element.contains("<h1 class=\"names\"> Сингл") || element.contains("<b>Перевод:</b> завершен") -> SManga.COMPLETED | ||||
|         element.contains("<b>Перевод:</b> продолжается") -> SManga.ONGOING | ||||
|         else -> SManga.UNKNOWN | ||||
|     } | ||||
|  | ||||
|     override fun chapterListSelector() = "div.chapters-link tbody tr" | ||||
|  | ||||
|     override fun chapterFromElement(element: Element): SChapter { | ||||
|         val urlElement = element.select("a").first() | ||||
|         val urlText = urlElement.text() | ||||
|  | ||||
|         val chapter = SChapter.create() | ||||
|         chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mtr=1") | ||||
|         if (urlText.endsWith(" новое")) { | ||||
|             chapter.name = urlText.dropLast(6) | ||||
|         } else { | ||||
|             chapter.name = urlText | ||||
|         } | ||||
|         chapter.date_upload = element.select("td.hidden-xxs").last()?.text()?.let { | ||||
|             SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time | ||||
|         } ?: 0 | ||||
|         return chapter | ||||
|     } | ||||
|  | ||||
|     override fun prepareNewChapter(chapter: SChapter, manga: SManga) { | ||||
|         val basic = Regex("""\s*([0-9]+)(\s-\s)([0-9]+)\s*""") | ||||
|         val extra = Regex("""\s*([0-9]+\sЭкстра)\s*""") | ||||
|         val single = Regex("""\s*Сингл\s*""") | ||||
|         when { | ||||
|             basic.containsMatchIn(chapter.name) -> { | ||||
|                 basic.find(chapter.name)?.let { | ||||
|                     val number = it.groups[3]?.value!! | ||||
|                     chapter.chapter_number = number.toFloat() | ||||
|                 } | ||||
|             } | ||||
|             extra.containsMatchIn(chapter.name) -> // Extra chapters doesn't contain chapter number | ||||
|                 chapter.chapter_number = -2f | ||||
|             single.containsMatchIn(chapter.name) -> // Oneshoots, doujinshi and other mangas with one chapter | ||||
|                 chapter.chapter_number = 1f | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun pageListParse(response: Response): List<Page> { | ||||
|         val html = response.body()!!.string() | ||||
|         val beginIndex = html.indexOf("rm_h.init( [") | ||||
|         val endIndex = html.indexOf("], 0, false);", beginIndex) | ||||
|         val trimmedHtml = html.substring(beginIndex, endIndex) | ||||
|  | ||||
|         val p = Pattern.compile("'.*?','.*?',\".*?\"") | ||||
|         val m = p.matcher(trimmedHtml) | ||||
|  | ||||
|         val pages = mutableListOf<Page>() | ||||
|  | ||||
|         var i = 0 | ||||
|         while (m.find()) { | ||||
|             val urlParts = m.group().replace("[\"\']+".toRegex(), "").split(',') | ||||
|             val url = if (urlParts[1].isEmpty() && urlParts[2].startsWith("/static/")) { | ||||
|                 baseUrl + urlParts[2] | ||||
|             } else { | ||||
|                 urlParts[1] + urlParts[0] + urlParts[2] | ||||
|             } | ||||
|             pages.add(Page(i++, "", url)) | ||||
|         } | ||||
|         return pages | ||||
|     } | ||||
|  | ||||
|     override fun pageListParse(document: Document): List<Page> { | ||||
|         throw Exception("Not used") | ||||
|     } | ||||
|  | ||||
|     override fun imageUrlParse(document: Document) = "" | ||||
|  | ||||
|     override fun imageRequest(page: Page): Request { | ||||
|         val imgHeader = Headers.Builder().apply { | ||||
|             add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") | ||||
|             add("Referer", baseUrl) | ||||
|         }.build() | ||||
|         return GET(page.imageUrl!!, imgHeader) | ||||
|     } | ||||
|  | ||||
|     private class Genre(name: String, val id: String) : Filter.TriState(name) | ||||
|     private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) | ||||
|     private class Category(categories: List<Genre>) : Filter.Group<Genre>("Category", categories) | ||||
|  | ||||
|     /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")] | ||||
|     *  .map(el => `Genre("${el.textContent.trim()}", $"{el.getAttribute('onclick') | ||||
|     *  .substr(31,el.getAttribute('onclick').length-33)"})`).join(',\n') | ||||
|     *  on http://readmanga.me/search/advanced | ||||
|     */ | ||||
|     override fun getFilterList() = FilterList( | ||||
|             Category(getCategoryList()), | ||||
|             GenreList(getGenreList()) | ||||
|     ) | ||||
|  | ||||
|     private fun getCategoryList() = listOf( | ||||
|             Genre("В цвете", "el_7290"), | ||||
|             Genre("Веб", "el_2160"), | ||||
|             Genre("Выпуск приостановлен", "el_8033"), | ||||
|             Genre("Ёнкома", "el_2161"), | ||||
|             Genre("Комикс западный", "el_3515"), | ||||
|             Genre("Манхва", "el_3001"), | ||||
|             Genre("Маньхуа", "el_3002"), | ||||
|             Genre("Ранобэ", "el_8575"), | ||||
|             Genre("Сборник", "el_2157") | ||||
|     ) | ||||
|  | ||||
|     private fun getGenreList() = 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