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:
Carlos 2018-02-13 09:53:41 -05:00 committed by inorichi
parent e333aab30f
commit 37dc44ee8a
6 changed files with 144 additions and 65 deletions

View File

@ -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"

View File

@ -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
}
}

View File

@ -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()
}

View File

@ -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()
)

View File

@ -0,0 +1,3 @@
package eu.kanade.tachiyomi.extension.all.mangadex
class MangaDexEnglish : Mangadex("en", "gb", 1)

View File

@ -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"