Mangadex fixes and improvements (#187)
* -added language support -fixed minor laguage paging issue -fixed some webtoons not working -adjusted browse by letter to be a dropdown * -added tristate to search with r18 * switched to es-419 for latin america * removed unneeded get all mangadex languages * commented out tristate for as cookie for r18 doesnt work anymore when not logged in * switched to SourceFactory * removed empty line * fixed missing info from manga details * Moved english to its own package to prevent having to migrate sources * optimized image url parsing to not load each page then each image.
This commit is contained in:
parent
e333aab30f
commit
37dc44ee8a
|
@ -0,0 +1,17 @@
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
appName = 'Tachiyomi: MangaDex'
|
||||||
|
pkgNameSuffix = "all.mangadex"
|
||||||
|
extClass = '.MangadexFactory'
|
||||||
|
extVersionCode = 4
|
||||||
|
extVersionSuffix = 4
|
||||||
|
libVersion = '1.2'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
|
@ -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.GET
|
||||||
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.source.model.*
|
import eu.kanade.tachiyomi.source.model.*
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
|
import rx.Observable
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.text.SimpleDateFormat
|
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"
|
override val name = "MangaDex"
|
||||||
|
|
||||||
|
@ -18,44 +21,40 @@ class Mangadex : ParsedHttpSource() {
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
override val lang = "en"
|
override val client = clientBuilder(ALL)
|
||||||
|
|
||||||
private val internalLang = "gb"
|
private fun clientBuilder(r18Toggle: Int): OkHttpClient = network.cloudflareClient.newBuilder()
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder()
|
|
||||||
.addNetworkInterceptor { chain ->
|
.addNetworkInterceptor { chain ->
|
||||||
val newReq = chain
|
val newReq = chain
|
||||||
.request()
|
.request()
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.addHeader("Cookie", cookiesHeader)
|
.addHeader("Cookie", cookiesHeader(r18Toggle))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
chain.proceed(newReq)
|
chain.proceed(newReq)
|
||||||
}.build()!!
|
}.build()!!
|
||||||
|
|
||||||
private val cookiesHeader by lazy {
|
private fun cookiesHeader(r18Toggle: Int): String {
|
||||||
val cookies = mutableMapOf<String, String>()
|
val cookies = mutableMapOf<String, String>()
|
||||||
cookies.put("mangadex_h_toggle", "1")
|
cookies.put("mangadex_h_toggle", r18Toggle.toString())
|
||||||
buildCookies(cookies)
|
return buildCookies(cookies)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildCookies(cookies: Map<String, String>)
|
private fun buildCookies(cookies: Map<String, String>) = cookies.entries.joinToString(separator = "; ", postfix = ";") {
|
||||||
= cookies.entries.map {
|
|
||||||
"${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}"
|
"${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}"
|
||||||
}.joinToString(separator = "; ", postfix = ";")
|
}
|
||||||
|
|
||||||
override fun popularMangaSelector() = ".table-responsive tbody tr"
|
override fun popularMangaSelector() = ".table-responsive tbody tr"
|
||||||
|
|
||||||
override fun latestUpdatesSelector() = ".table-responsive tbody tr a.manga_title[href*=manga]"
|
override fun latestUpdatesSelector() = ".table-responsive tbody tr a.manga_title[href*=manga]"
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
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)
|
return GET("$baseUrl/titles$pageStr", headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request {
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
val pageStr = if (page != 1) ((page * 20) - 20) else ""
|
val pageStr = if (page != 1) "/" + ((page * 20)) else ""
|
||||||
return GET("$baseUrl/1/$page", headers)
|
return GET("$baseUrl/$pageStart$pageStr", headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaFromElement(element: Element): SManga {
|
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 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 {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val byGenre = filters.find { it is GenreList }
|
val byGenre = filters.find { it is GenreList }
|
||||||
val genres = mutableListOf<String>()
|
val genres = mutableListOf<String>()
|
||||||
|
@ -96,8 +121,9 @@ class Mangadex : ParsedHttpSource() {
|
||||||
}
|
}
|
||||||
//do browse by letter if set
|
//do browse by letter if set
|
||||||
val byLetter = filters.find { it is ByLetter }
|
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 pageStr = if (page != 1) (((page - 1) * 100)).toString() else "0"
|
||||||
val url = HttpUrl.parse("$baseUrl/titles/")!!.newBuilder().addPathSegment(s).addPathSegment(pageStr)
|
val url = HttpUrl.parse("$baseUrl/titles/")!!.newBuilder().addPathSegment(s).addPathSegment(pageStr)
|
||||||
return GET(url.toString(), headers)
|
return GET(url.toString(), headers)
|
||||||
|
@ -124,15 +150,14 @@ class Mangadex : ParsedHttpSource() {
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document): SManga {
|
override fun mangaDetailsParse(document: Document): SManga {
|
||||||
val manga = SManga.create()
|
val manga = SManga.create()
|
||||||
val imageElement = document.select(".table-condensed").first()
|
val infoElement = document.select(".row.edit").first()
|
||||||
val infoElement = document.select(".table.table-condensed.edit").first()
|
|
||||||
val genreElement = infoElement.select("tr:eq(3) td .genre")
|
val genreElement = infoElement.select("tr:eq(3) td .genre")
|
||||||
|
|
||||||
manga.author = infoElement.select("tr:eq(1) td").first()?.text()
|
manga.author = infoElement.select("tr:eq(1) td").first()?.text()
|
||||||
manga.artist = infoElement.select("tr:eq(2) 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.status = parseStatus(infoElement.select("tr:eq(5) td").first()?.text())
|
||||||
manga.description = infoElement.select("tr:eq(7) 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>()
|
var genres = mutableListOf<String>()
|
||||||
genreElement?.forEach { genres.add(it.text()) }
|
genreElement?.forEach { genres.add(it.text()) }
|
||||||
manga.genre = genres.joinToString(", ")
|
manga.genre = genres.joinToString(", ")
|
||||||
|
@ -162,30 +187,21 @@ class Mangadex : ParsedHttpSource() {
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
val pages = mutableListOf<Page>()
|
val pages = mutableListOf<Page>()
|
||||||
val url = document.baseUri()
|
val url = document.baseUri()
|
||||||
val select = document.select("#jump_page")
|
|
||||||
//if its a regular manga get the pages from the drop down selector
|
val dataUrl = document.select("script").last().html().substringAfter("dataurl = '").substringBefore("';")
|
||||||
if (select.isNotEmpty()) {
|
val imageUrl = document.select("script").last().html().substringAfter("page_array = [").substringBefore("];")
|
||||||
select.first().select("option").forEach {
|
val listImageUrls = imageUrl.replace("'", "").split(",")
|
||||||
pages.add(Page(pages.size, url + "/" + it.attr("value")))
|
val server = document.select("script").last().html().substringAfter("server = '").substringBefore("';")
|
||||||
}
|
|
||||||
} else {
|
listImageUrls.filter { it.isNotBlank() }.forEach {
|
||||||
//webtoon get all the image urls on the one page
|
val url = "$server$dataUrl/$it"
|
||||||
document.select(".edit.webtoon").forEach {
|
pages.add(Page(pages.size, "", getImageUrl(url)))
|
||||||
pages.add(Page(pages.size, "", it.attr("src")))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pages
|
return pages
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document): String {
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseStatus(status: String?) = when {
|
private fun parseStatus(status: String?) = when {
|
||||||
status == null -> SManga.UNKNOWN
|
status == null -> SManga.UNKNOWN
|
||||||
|
@ -195,19 +211,28 @@ class Mangadex : ParsedHttpSource() {
|
||||||
else -> SManga.UNKNOWN
|
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 TextField(name: String, val key: String) : Filter.Text(name)
|
||||||
private class Genre(val id: String, name: String) : Filter.CheckBox(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 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(
|
override fun getFilterList() = FilterList(
|
||||||
TextField("Author", "author"),
|
TextField("Author", "author"),
|
||||||
TextField("Artist", "artist"),
|
TextField("Artist", "artist"),
|
||||||
|
//R18("Show R18+"),
|
||||||
GenreList(getGenreList()),
|
GenreList(getGenreList()),
|
||||||
Filter.Header("Note: Browsing by Letter"),
|
ByLetter(listOf(Letters()))
|
||||||
Filter.Header("Ignores other Search Fields"),
|
)
|
||||||
ByLetter())
|
|
||||||
|
|
||||||
|
|
||||||
private fun getGenreList() = listOf(
|
private fun getGenreList() = listOf(
|
||||||
|
@ -252,4 +277,10 @@ class Mangadex : ParsedHttpSource() {
|
||||||
Genre("39", "[no chapters]"),
|
Genre("39", "[no chapters]"),
|
||||||
Genre("40", "Game")
|
Genre("40", "Game")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val NO_R18 = 0
|
||||||
|
const val ALL = 1
|
||||||
|
const val ONLY_R18 = 2
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.all.mangadex
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.source.SourceFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Carlos on 2/8/2018.
|
||||||
|
*/
|
||||||
|
class MangadexFactory : SourceFactory {
|
||||||
|
override fun createSources(): List<Source> = getAllMangaDexLanguages()
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.all.mangadex
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mangadex languages
|
||||||
|
*/
|
||||||
|
|
||||||
|
class MangaDexPolish : Mangadex("pl", "pl", 3)
|
||||||
|
class MangaDexItalian : Mangadex("it", "it", 6)
|
||||||
|
class MangaDexRussian : Mangadex("ru", "ru", 7)
|
||||||
|
class MangaDexGerman : Mangadex("de", "de", 8)
|
||||||
|
class MangaDexFrench : Mangadex("fr", "fr", 10)
|
||||||
|
class MangaDexVietnamese : Mangadex("vi", "vn", 12)
|
||||||
|
class MangaDexSpanishSpain : Mangadex("es", "es", 15)
|
||||||
|
class MangaDexPortuguese : Mangadex("pt", "br", 16)
|
||||||
|
class MangaDexSwedish : Mangadex("sv", "se", 18)
|
||||||
|
class MangaDexTurkish : Mangadex("tr", "tr", 26)
|
||||||
|
class MangaDexIndonesian : Mangadex("id", "id", 27)
|
||||||
|
class MangaDexSpanishLTAM : Mangadex("es-419", "mx", 29)
|
||||||
|
class MangaDexCatalan : Mangadex("ca", "ct", 33)
|
||||||
|
|
||||||
|
fun getAllMangaDexLanguages() = listOf(
|
||||||
|
MangaDexEnglish(),
|
||||||
|
MangaDexPolish(),
|
||||||
|
MangaDexItalian(),
|
||||||
|
MangaDexRussian(),
|
||||||
|
MangaDexGerman(),
|
||||||
|
MangaDexFrench(),
|
||||||
|
MangaDexVietnamese(),
|
||||||
|
MangaDexSpanishSpain(),
|
||||||
|
MangaDexPortuguese(),
|
||||||
|
MangaDexSwedish(),
|
||||||
|
MangaDexTurkish(),
|
||||||
|
MangaDexIndonesian(),
|
||||||
|
MangaDexSpanishLTAM(),
|
||||||
|
MangaDexCatalan()
|
||||||
|
)
|
|
@ -0,0 +1,3 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.all.mangadex
|
||||||
|
|
||||||
|
class MangaDexEnglish : Mangadex("en", "gb", 1)
|
|
@ -1,19 +0,0 @@
|
||||||
apply plugin: 'com.android.application'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
|
|
||||||
|
|
||||||
ext {
|
|
||||||
appName = 'Tachiyomi: MangaDex'
|
|
||||||
pkgNameSuffix = "en.mangadex"
|
|
||||||
extClass = '.Mangadex'
|
|
||||||
extVersionCode = 3
|
|
||||||
extVersionSuffix = 3
|
|
||||||
libVersion = '1.2'
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
provided "com.google.code.gson:gson:2.8.0"
|
|
||||||
provided "com.github.salomonbrys.kotson:kotson:2.5.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Loading…
Reference in New Issue