|
|
|
@ -1,16 +1,19 @@
|
|
|
|
|
package eu.kanade.tachiyomi.extension.en.mangadex
|
|
|
|
|
package eu.kanade.tachiyomi.extension.all.mangadex
|
|
|
|
|
|
|
|
|
|
import eu.kanade.tachiyomi.network.GET
|
|
|
|
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
|
|
|
|
import eu.kanade.tachiyomi.source.model.*
|
|
|
|
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
|
|
|
|
import okhttp3.HttpUrl
|
|
|
|
|
import okhttp3.OkHttpClient
|
|
|
|
|
import okhttp3.Request
|
|
|
|
|
import org.jsoup.nodes.Document
|
|
|
|
|
import org.jsoup.nodes.Element
|
|
|
|
|
import rx.Observable
|
|
|
|
|
import java.net.URLEncoder
|
|
|
|
|
import java.text.SimpleDateFormat
|
|
|
|
|
|
|
|
|
|
class Mangadex : ParsedHttpSource() {
|
|
|
|
|
open class Mangadex(override val lang: String, private val internalLang: String, val pageStart: Int) : ParsedHttpSource() {
|
|
|
|
|
|
|
|
|
|
override val name = "MangaDex"
|
|
|
|
|
|
|
|
|
@ -18,44 +21,40 @@ class Mangadex : ParsedHttpSource() {
|
|
|
|
|
|
|
|
|
|
override val supportsLatest = true
|
|
|
|
|
|
|
|
|
|
override val lang = "en"
|
|
|
|
|
override val client = clientBuilder(ALL)
|
|
|
|
|
|
|
|
|
|
private val internalLang = "gb"
|
|
|
|
|
|
|
|
|
|
override val client = network.cloudflareClient.newBuilder()
|
|
|
|
|
private fun clientBuilder(r18Toggle: Int): OkHttpClient = network.cloudflareClient.newBuilder()
|
|
|
|
|
.addNetworkInterceptor { chain ->
|
|
|
|
|
val newReq = chain
|
|
|
|
|
.request()
|
|
|
|
|
.newBuilder()
|
|
|
|
|
.addHeader("Cookie", cookiesHeader)
|
|
|
|
|
.addHeader("Cookie", cookiesHeader(r18Toggle))
|
|
|
|
|
.build()
|
|
|
|
|
|
|
|
|
|
chain.proceed(newReq)
|
|
|
|
|
}.build()!!
|
|
|
|
|
|
|
|
|
|
private val cookiesHeader by lazy {
|
|
|
|
|
private fun cookiesHeader(r18Toggle: Int): String {
|
|
|
|
|
val cookies = mutableMapOf<String, String>()
|
|
|
|
|
cookies.put("mangadex_h_toggle", "1")
|
|
|
|
|
buildCookies(cookies)
|
|
|
|
|
cookies.put("mangadex_h_toggle", r18Toggle.toString())
|
|
|
|
|
return buildCookies(cookies)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun buildCookies(cookies: Map<String, String>)
|
|
|
|
|
= cookies.entries.map {
|
|
|
|
|
private fun buildCookies(cookies: Map<String, String>) = cookies.entries.joinToString(separator = "; ", postfix = ";") {
|
|
|
|
|
"${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}"
|
|
|
|
|
}.joinToString(separator = "; ", postfix = ";")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun popularMangaSelector() = ".table-responsive tbody tr"
|
|
|
|
|
|
|
|
|
|
override fun latestUpdatesSelector() = ".table-responsive tbody tr a.manga_title[href*=manga]"
|
|
|
|
|
|
|
|
|
|
override fun popularMangaRequest(page: Int): Request {
|
|
|
|
|
val pageStr = if (page != 1) "//" + ((page * 100) - 100) else ""
|
|
|
|
|
val pageStr = if (page != 1) "/" + ((page * 100) - 100) else ""
|
|
|
|
|
return GET("$baseUrl/titles$pageStr", headers)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun latestUpdatesRequest(page: Int): Request {
|
|
|
|
|
val pageStr = if (page != 1) ((page * 20) - 20) else ""
|
|
|
|
|
return GET("$baseUrl/1/$page", headers)
|
|
|
|
|
val pageStr = if (page != 1) "/" + ((page * 20)) else ""
|
|
|
|
|
return GET("$baseUrl/$pageStart$pageStr", headers)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun popularMangaFromElement(element: Element): SManga {
|
|
|
|
@ -83,6 +82,32 @@ class Mangadex : ParsedHttpSource() {
|
|
|
|
|
|
|
|
|
|
override fun searchMangaNextPageSelector() = ".pagination li:not(.disabled) span[title*=last page]:not(disabled)"
|
|
|
|
|
|
|
|
|
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
|
|
|
|
return getSearchClient(filters).newCall(searchMangaRequest(page, query, filters))
|
|
|
|
|
.asObservableSuccess()
|
|
|
|
|
.map { response ->
|
|
|
|
|
searchMangaParse(response)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* get search client based off r18 filter. This will always return default client builder now until r18 solution is found or login is add
|
|
|
|
|
*/
|
|
|
|
|
private fun getSearchClient(filters: FilterList): OkHttpClient {
|
|
|
|
|
filters.forEach { filter ->
|
|
|
|
|
when (filter) {
|
|
|
|
|
is R18 -> {
|
|
|
|
|
return when {
|
|
|
|
|
filter.isExcluded() -> clientBuilder(NO_R18)
|
|
|
|
|
filter.isIncluded() -> clientBuilder(ONLY_R18)
|
|
|
|
|
else -> clientBuilder(ALL)
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return clientBuilder(ALL)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
|
|
|
|
val byGenre = filters.find { it is GenreList }
|
|
|
|
|
val genres = mutableListOf<String>()
|
|
|
|
@ -96,8 +121,9 @@ class Mangadex : ParsedHttpSource() {
|
|
|
|
|
}
|
|
|
|
|
//do browse by letter if set
|
|
|
|
|
val byLetter = filters.find { it is ByLetter }
|
|
|
|
|
if (byLetter != null && (byLetter as ByLetter).state != 0) {
|
|
|
|
|
val s = byLetter.values[byLetter.state]
|
|
|
|
|
|
|
|
|
|
if (byLetter != null && (byLetter as ByLetter).state.first().state != 0) {
|
|
|
|
|
val s = byLetter.state.first().values[byLetter.state.first().state]
|
|
|
|
|
val pageStr = if (page != 1) (((page - 1) * 100)).toString() else "0"
|
|
|
|
|
val url = HttpUrl.parse("$baseUrl/titles/")!!.newBuilder().addPathSegment(s).addPathSegment(pageStr)
|
|
|
|
|
return GET(url.toString(), headers)
|
|
|
|
@ -124,15 +150,14 @@ class Mangadex : ParsedHttpSource() {
|
|
|
|
|
|
|
|
|
|
override fun mangaDetailsParse(document: Document): SManga {
|
|
|
|
|
val manga = SManga.create()
|
|
|
|
|
val imageElement = document.select(".table-condensed").first()
|
|
|
|
|
val infoElement = document.select(".table.table-condensed.edit").first()
|
|
|
|
|
val infoElement = document.select(".row.edit").first()
|
|
|
|
|
val genreElement = infoElement.select("tr:eq(3) td .genre")
|
|
|
|
|
|
|
|
|
|
manga.author = infoElement.select("tr:eq(1) td").first()?.text()
|
|
|
|
|
manga.artist = infoElement.select("tr:eq(2) td").first()?.text()
|
|
|
|
|
manga.status = parseStatus(infoElement.select("tr:eq(5) td").first()?.text())
|
|
|
|
|
manga.description = infoElement.select("tr:eq(7) td").first()?.text()
|
|
|
|
|
manga.thumbnail_url = imageElement.select("img").first()?.attr("src").let { baseUrl + "/" + it }
|
|
|
|
|
manga.thumbnail_url = infoElement.select("img").first()?.attr("src").let { baseUrl + "/" + it }
|
|
|
|
|
var genres = mutableListOf<String>()
|
|
|
|
|
genreElement?.forEach { genres.add(it.text()) }
|
|
|
|
|
manga.genre = genres.joinToString(", ")
|
|
|
|
@ -162,30 +187,21 @@ class Mangadex : ParsedHttpSource() {
|
|
|
|
|
override fun pageListParse(document: Document): List<Page> {
|
|
|
|
|
val pages = mutableListOf<Page>()
|
|
|
|
|
val url = document.baseUri()
|
|
|
|
|
val select = document.select("#jump_page")
|
|
|
|
|
//if its a regular manga get the pages from the drop down selector
|
|
|
|
|
if (select.isNotEmpty()) {
|
|
|
|
|
select.first().select("option").forEach {
|
|
|
|
|
pages.add(Page(pages.size, url + "/" + it.attr("value")))
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
//webtoon get all the image urls on the one page
|
|
|
|
|
document.select(".edit.webtoon").forEach {
|
|
|
|
|
pages.add(Page(pages.size, "", it.attr("src")))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val dataUrl = document.select("script").last().html().substringAfter("dataurl = '").substringBefore("';")
|
|
|
|
|
val imageUrl = document.select("script").last().html().substringAfter("page_array = [").substringBefore("];")
|
|
|
|
|
val listImageUrls = imageUrl.replace("'", "").split(",")
|
|
|
|
|
val server = document.select("script").last().html().substringAfter("server = '").substringBefore("';")
|
|
|
|
|
|
|
|
|
|
listImageUrls.filter { it.isNotBlank() }.forEach {
|
|
|
|
|
val url = "$server$dataUrl/$it"
|
|
|
|
|
pages.add(Page(pages.size, "", getImageUrl(url)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pages
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun imageUrlParse(document: Document): String {
|
|
|
|
|
val attr = document.select("#current_page").first().attr("src")
|
|
|
|
|
//some images are hosted elsewhere
|
|
|
|
|
if (attr.startsWith("http")) {
|
|
|
|
|
return attr
|
|
|
|
|
}
|
|
|
|
|
return baseUrl + attr
|
|
|
|
|
}
|
|
|
|
|
override fun imageUrlParse(document: Document): String = ""
|
|
|
|
|
|
|
|
|
|
private fun parseStatus(status: String?) = when {
|
|
|
|
|
status == null -> SManga.UNKNOWN
|
|
|
|
@ -195,19 +211,28 @@ class Mangadex : ParsedHttpSource() {
|
|
|
|
|
else -> SManga.UNKNOWN
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun getImageUrl(attr: String): String {
|
|
|
|
|
//some images are hosted elsewhere
|
|
|
|
|
if (attr.startsWith("http")) {
|
|
|
|
|
return attr
|
|
|
|
|
}
|
|
|
|
|
return baseUrl + attr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class TextField(name: String, val key: String) : Filter.Text(name)
|
|
|
|
|
private class Genre(val id: String, name: String) : Filter.CheckBox(name)
|
|
|
|
|
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
|
|
|
|
|
private class ByLetter : Filter.Select<String>("Browse By Letter", arrayOf("", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"))
|
|
|
|
|
|
|
|
|
|
private class R18(name: String) : Filter.TriState(name)
|
|
|
|
|
private class ByLetter(letters: List<Letters>) : Filter.Group<Letters>("Browse by Letter only", letters)
|
|
|
|
|
private class Letters : Filter.Select<String>("Letter", arrayOf("", "~", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"))
|
|
|
|
|
|
|
|
|
|
override fun getFilterList() = FilterList(
|
|
|
|
|
TextField("Author", "author"),
|
|
|
|
|
TextField("Artist", "artist"),
|
|
|
|
|
//R18("Show R18+"),
|
|
|
|
|
GenreList(getGenreList()),
|
|
|
|
|
Filter.Header("Note: Browsing by Letter"),
|
|
|
|
|
Filter.Header("Ignores other Search Fields"),
|
|
|
|
|
ByLetter())
|
|
|
|
|
ByLetter(listOf(Letters()))
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private fun getGenreList() = listOf(
|
|
|
|
@ -252,4 +277,10 @@ class Mangadex : ParsedHttpSource() {
|
|
|
|
|
Genre("39", "[no chapters]"),
|
|
|
|
|
Genre("40", "Game")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
const val NO_R18 = 0
|
|
|
|
|
const val ALL = 1
|
|
|
|
|
const val ONLY_R18 = 2
|
|
|
|
|
}
|
|
|
|
|
}
|