Fix mangalib (#1202)

Fix mangalib
This commit is contained in:
red 2019-06-19 22:11:06 +03:00 committed by Eugene
parent f27da3e9cb
commit 291c54e2d4
2 changed files with 200 additions and 144 deletions

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: LibManga' appName = 'Tachiyomi: LibManga'
pkgNameSuffix = 'ru.libmanga' pkgNameSuffix = 'ru.libmanga'
extClass = '.LibMangaFactory' extClass = '.LibMangaFactory'
extVersionCode = 4 extVersionCode = 5
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -1,51 +1,124 @@
package eu.kanade.tachiyomi.extension.ru.libmanga package eu.kanade.tachiyomi.extension.ru.libmanga
import com.github.salomonbrys.kotson.* import com.github.salomonbrys.kotson.*
import com.google.gson.JsonElement
import com.google.gson.JsonParser import com.google.gson.JsonParser
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
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.HttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl import okhttp3.*
import okhttp3.Request
import okhttp3.Response
import org.json.JSONObject
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import android.util.Base64.decode as base64Decode
import rx.Observable
open class LibManga(override val name: String, override val baseUrl: String, private val staticUrl: String) : ParsedHttpSource() { open class LibManga(override val name: String, override val baseUrl: String, private val staticUrl: String) : HttpSource() {
override val lang = "ru" override val lang = "ru"
override val supportsLatest = true override val supportsLatest = true
override fun popularMangaRequest(page: Int): Request = override val client: OkHttpClient = network.cloudflareClient
GET("$baseUrl/manga-list?dir=desc&page=$page&sort=views", headers)
override fun popularMangaSelector() = "div.manga-list-item" private val jsonParser = JsonParser()
override fun popularMangaFromElement(element: Element): SManga { override fun latestUpdatesRequest(page: Int) = GET(baseUrl, headers)
val item = element.select("a.manga-list-item__content").first()
private val latestUpdatesSelector = "div.updates__left"
override fun latestUpdatesParse(response: Response): MangasPage {
val elements = response.asJsoup().select(latestUpdatesSelector)
val latestMangas = elements?.map { latestUpdatesFromElement(it) }
if (latestMangas != null)
return MangasPage(latestMangas,false) // TODO: use API
return MangasPage(emptyList(), false)
}
private fun latestUpdatesFromElement(element: Element): SManga {
val link = element.select("a").first()
val img = link.select("img").first()
val manga = SManga.create() val manga = SManga.create()
manga.thumbnail_url = item.attr("data-src") manga.thumbnail_url = img.attr("data-src")
manga.setUrlWithoutDomain(item.attr("href")) .replace("cover_thumb", "cover_250x350")
manga.title = item.select("h3.manga-list-item__name").first().text() manga.setUrlWithoutDomain(link.attr("href"))
manga.title = img.attr("alt")
return manga return manga
} }
override fun popularMangaNextPageSelector() = "a[rel=\"next\"]" private var csrfToken: String = ""
override fun mangaDetailsParse(document: Document): SManga { private fun catalogHeaders() = Headers.Builder()
.apply {
add("Accept", "application/json, text/plain, */*")
add("X-Requested-With", "XMLHttpRequest")
add("x-csrf-token", csrfToken)
}
.build()
override fun popularMangaRequest(page: Int) = GET("$baseUrl/login", headers)
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
if (csrfToken.isEmpty()) {
return client.newCall(popularMangaRequest(page))
.asObservableSuccess()
.flatMap { response ->
// Obtain token
val resBody = response.body()!!.string()
csrfToken = "_token\" content=\"(.*)\"".toRegex().find(resBody)!!.groups[1]!!.value
return@flatMap fetchPopularMangaFromApi(page)
}
}
return fetchPopularMangaFromApi(page)
}
private fun fetchPopularMangaFromApi(page : Int): Observable<MangasPage> {
return client.newCall(POST("$baseUrl/filterlist?dir=desc&sort=views&page=$page", catalogHeaders()))
.asObservableSuccess()
.map { response ->
popularMangaParse(response)
}
}
override fun popularMangaParse(response: Response): MangasPage {
val resBody = response.body()!!.string()
val result = jsonParser.parse(resBody).obj
val items = result["items"]
val popularMangas = items["data"].nullArray?.map { popularMangaFromElement(it) }
if (popularMangas != null) {
val hasNextPage = items["next_page_url"].nullString != null
return MangasPage(popularMangas, hasNextPage)
}
return MangasPage(emptyList(), false)
}
private fun popularMangaFromElement(el: JsonElement) = SManga.create().apply {
title = el["name"].string
thumbnail_url = "$baseUrl/uploads/" + if (el["cover"].nullInt != null)
"cover/${el["slug"].string}/cover/cover_250x350.jpg" else
"no-image.png"
url = "/" + el["slug"].string
}
override fun mangaDetailsParse(response: Response): SManga {
val document = response.asJsoup()
val body = document.select("div.section__body").first() val body = document.select("div.section__body").first()
val manga = SManga.create() val manga = SManga.create()
manga.title = body.select(".manga__title").text() manga.title = body.select(".manga__title").text()
manga.thumbnail_url = body.select(".manga__cover").attr("src") manga.thumbnail_url = body.select(".manga__cover").attr("src")
manga.author = body.select(".info-list__row:nth-child(2) > a").text() manga.author = body.select(".info-list__row:nth-child(2) > a").text()
manga.artist = body.select(".info-list__row:nth-child(3) > a").text() manga.artist = body.select(".info-list__row:nth-child(3) > a").text()
manga.status = when (body.select(".info-list__row:nth-child(4) > span").text()) { manga.status = when (
body.select(".info-list__row:has(strong:contains(Перевод))")
.first()
.select("span.m-label_info")
.text())
{
"продолжается" -> SManga.ONGOING "продолжается" -> SManga.ONGOING
"завершен" -> SManga.COMPLETED "завершен" -> SManga.COMPLETED
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
@ -55,9 +128,15 @@ open class LibManga(override val name: String, override val baseUrl: String, pri
return manga return manga
} }
override fun chapterListSelector() = "div.chapter-item" private val chapterListSelector = "div.chapter-item"
override fun chapterFromElement(element: Element): SChapter { override fun chapterListParse(response: Response): List<SChapter> {
val elements = response.asJsoup().select(chapterListSelector)
val chapters = elements?.map { chapterFromElement(it) }
return chapters ?: emptyList()
}
private fun chapterFromElement(element: Element): SChapter {
val chapterLink = element.select("div.chapter-item__name > a").first() val chapterLink = element.select("div.chapter-item__name > a").first()
val chapter = SChapter.create() val chapter = SChapter.create()
chapter.setUrlWithoutDomain(chapterLink.attr("href")) chapter.setUrlWithoutDomain(chapterLink.attr("href"))
@ -74,44 +153,40 @@ open class LibManga(override val name: String, override val baseUrl: String, pri
} }
} }
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
val chapInfo = document
.select("script:containsData(window.__info)")
.first()
.html()
.replace("window.__info = ", "")
.replace(";", "")
val chapInfoJson = jsonParser.parse(chapInfo).obj
// Get pages
val baseStr = document.select("span.pp")
.first()
.html()
.replace("<!--", "")
.replace("-->", "")
.trim()
val decodedArr = base64Decode(baseStr, android.util.Base64.DEFAULT)
val pagesJson = jsonParser.parse(String(decodedArr)).array
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
// Parse script pagesJson.forEach { page ->
val script = document.select("script:containsData(window.__info)").first().html() pages.add(Page(page["p"].int, "", staticUrl + chapInfoJson["imgUrl"].string + page["u"].string))
val json: String = script.replace("window.__info = ", "")
val chapterInfo = JSONObject(json)
val pagesJson = chapterInfo.getJSONArray("pages")
for (i in 0..(pagesJson.length() - 1)) {
val page = pagesJson.getJSONObject(i)
pages.add(Page(page.getInt("page_slug"), "", staticUrl + chapterInfo.getString("imgUrl") + page.getString("page_image")))
} }
return pages return pages
} }
override fun imageUrlParse(document: Document) = "" override fun imageUrlParse(response: Response): String = ""
override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, headers)
override fun latestUpdatesSelector() = "div.updates__left"
override fun latestUpdatesFromElement(element: Element): SManga {
val link = element.select("a").first()
val img = link.select("img").first()
val manga = SManga.create()
manga.thumbnail_url = img.attr("data-src")
manga.setUrlWithoutDomain(link.attr("href"))
manga.title = img.attr("alt")
return manga
}
override fun latestUpdatesNextPageSelector(): String? = null
override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = HttpUrl.parse("$baseUrl/manga-list?page=$page")!!.newBuilder() val url = HttpUrl.parse("$baseUrl/filterlist?page=$page")!!.newBuilder()
if (query.isNotEmpty()) { if (query.isNotEmpty()) {
url.addQueryParameter("name", query) url.addQueryParameter("name", query)
} }
@ -134,18 +209,18 @@ open class LibManga(override val name: String, override val baseUrl: String, pri
} }
is OrderBy -> { is OrderBy -> {
url.addQueryParameter("dir", if (filter.state!!.ascending) "asc" else "desc") url.addQueryParameter("dir", if (filter.state!!.ascending) "asc" else "desc")
url.addQueryParameter("sort", arrayOf("rate", "name", "views", "created_at")[filter.state!!.index]) url.addQueryParameter("sort", arrayOf("rate", "name", "views", "created_at", "chap_count")[filter.state!!.index])
} }
} }
} }
return GET(url.toString(), headers) return POST(url.toString(), catalogHeaders())
} }
// Hack search method to add some results from search popup // Hack search method to add some results from search popup
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
val searchRequest = response.request().url().queryParameter("name") val searchRequest = response.request().url().queryParameter("name")
val mangas = mutableListOf<SManga>() val mangas = mutableListOf<SManga>()
if (!searchRequest.isNullOrEmpty()) { if (!searchRequest.isNullOrEmpty()) {
val popupSearchHeaders = headers val popupSearchHeaders = headers
.newBuilder() .newBuilder()
@ -155,43 +230,24 @@ open class LibManga(override val name: String, override val baseUrl: String, pri
// +200ms // +200ms
val popup = client.newCall( val popup = client.newCall(
GET("https://mangalib.me/search?query=$searchRequest", headers = popupSearchHeaders) GET("$baseUrl/search?query=$searchRequest", popupSearchHeaders))
).execute().body()!!.string() .execute().body()!!.string()
val jsonList = JsonParser().parse(popup).asJsonArray
val jsonList = jsonParser.parse(popup).array
jsonList.forEach { jsonList.forEach {
val element = it.asJsonObject mangas.add(popularMangaFromElement(it))
val manga = SManga.create()
manga.setUrlWithoutDomain("/" + element.get("slug").string)
manga.description = element.get("summary").nullString
manga.author = element.get("author").nullString
manga.title = element.get("name").string
val status = element.get("status_id").int
manga.status = if (status > 2) 2 else status
mangas.add(manga)
} }
}
val document = response.asJsoup()
val searchedMangas = document.select(searchMangaSelector()).map { element ->
searchMangaFromElement(element)
} }
val searchedMangas = popularMangaParse(response)
// Filtered out what find in popup search // Filtered out what find in popup search
mangas.addAll(searchedMangas.filter { search -> mangas.addAll(searchedMangas.mangas.filter { search ->
mangas.find { search.title == it.title } == null mangas.find { search.title == it.title } == null
}) })
return MangasPage(mangas, searchedMangas.hasNextPage)
val hasNextPage = searchMangaNextPageSelector().let { selector ->
document.select(selector).first()
} != null
return MangasPage(mangas, hasNextPage)
} }
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
private class SearchFilter(name: String, val id: String) : Filter.TriState(name) private class SearchFilter(name: String, val id: String) : Filter.TriState(name)
private class CategoryList(categories: List<SearchFilter>) : Filter.Group<SearchFilter>("Категории", categories) private class CategoryList(categories: List<SearchFilter>) : Filter.Group<SearchFilter>("Категории", categories)
@ -199,15 +255,15 @@ open class LibManga(override val name: String, override val baseUrl: String, pri
private class GenreList(genres: List<SearchFilter>) : Filter.Group<SearchFilter>("Жанры", genres) private class GenreList(genres: List<SearchFilter>) : Filter.Group<SearchFilter>("Жанры", genres)
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
CategoryList(getCategoryList()), CategoryList(getCategoryList()),
StatusList(getStatusList()), StatusList(getStatusList()),
GenreList(getGenreList()), GenreList(getGenreList()),
OrderBy() OrderBy()
) )
private class OrderBy : Filter.Sort("Сортировка", private class OrderBy : Filter.Sort("Сортировка",
arrayOf("Рейтинг", "Имя", "Просмотры", "Дата"), arrayOf("Рейтинг", "Имя", "Просмотры", "Дата", "Кол-во глав"),
Filter.Sort.Selection(0, false)) Filter.Sort.Selection(0, false))
/* /*
* Use console * Use console
@ -215,13 +271,13 @@ open class LibManga(override val name: String, override val baseUrl: String, pri
* on /manga-list * on /manga-list
*/ */
private fun getCategoryList() = listOf( private fun getCategoryList() = listOf(
SearchFilter("Манга", "1"), SearchFilter("Манга", "1"),
SearchFilter("OEL-манга", "4"), SearchFilter("OEL-манга", "4"),
SearchFilter("Манхва", "5"), SearchFilter("Манхва", "5"),
SearchFilter("Маньхуа", "6"), SearchFilter("Маньхуа", "6"),
SearchFilter("Сингл", "7"), SearchFilter("Сингл", "7"),
SearchFilter("Руманга", "8"), SearchFilter("Руманга", "8"),
SearchFilter("Комикс западный", "9") SearchFilter("Комикс западный", "9")
) )
/* /*
@ -230,9 +286,9 @@ open class LibManga(override val name: String, override val baseUrl: String, pri
* on /manga-list * on /manga-list
*/ */
private fun getStatusList() = listOf( private fun getStatusList() = listOf(
SearchFilter("Продолжается", "1"), SearchFilter("Продолжается", "1"),
SearchFilter("Завершен", "2"), SearchFilter("Завершен", "2"),
SearchFilter("Заморожен", "3") SearchFilter("Заморожен", "3")
) )
/* /*
@ -241,51 +297,51 @@ open class LibManga(override val name: String, override val baseUrl: String, pri
* on /manga-list * on /manga-list
*/ */
private fun getGenreList() = listOf( private fun getGenreList() = listOf(
SearchFilter("арт", "32"), SearchFilter("арт", "32"),
SearchFilter("бара", "33"), SearchFilter("боевик", "34"),
SearchFilter("боевик", "34"), SearchFilter("боевые искусства", "35"),
SearchFilter("боевые искусства", "35"), SearchFilter("вампиры", "36"),
SearchFilter("вампиры", "36"), SearchFilter("веб", "78"),
SearchFilter("гарем", "37"), SearchFilter("гарем", "37"),
SearchFilter("гендерная интрига", "38"), SearchFilter("гендерная интрига", "38"),
SearchFilter("героическое фэнтези", "39"), SearchFilter("героическое фэнтези", "39"),
SearchFilter("детектив", "40"), SearchFilter("детектив", "40"),
SearchFilter("дзёсэй", "41"), SearchFilter("дзёсэй", "41"),
SearchFilter("додзинси", "42"), SearchFilter("додзинси", "42"),
SearchFilter("драма", "43"), SearchFilter("драма", "43"),
SearchFilter("игра", "44"), SearchFilter("ёнкома", "75"),
SearchFilter("история", "45"), SearchFilter("игра", "44"),
SearchFilter("киберпанк", "46"), SearchFilter("история", "45"),
SearchFilter("комедия", "47"), SearchFilter("киберпанк", "46"),
SearchFilter("махо-сёдзё", "48"), SearchFilter("кодомо", "76"),
SearchFilter("меха", "49"), SearchFilter("комедия", "47"),
SearchFilter("мистика", "50"), SearchFilter("махо-сёдзё", "48"),
SearchFilter("научная фантастика", "51"), SearchFilter("меха", "49"),
SearchFilter("повседневность", "52"), SearchFilter("мистика", "50"),
SearchFilter("постапокалиптика", "53"), SearchFilter("научная фантастика", "51"),
SearchFilter("приключения", "54"), SearchFilter("омегаверс", "77"),
SearchFilter("психология", "55"), SearchFilter("повседневность", "52"),
SearchFilter("романтика", "56"), SearchFilter("постапокалиптика", "53"),
SearchFilter("самурайский боевик", "57"), SearchFilter("приключения", "54"),
SearchFilter("сверхъестественное", "58"), SearchFilter("психология", "55"),
SearchFilter("сёдзё", "59"), SearchFilter("романтика", "56"),
SearchFilter("сёдзё-ай", "60"), SearchFilter("самурайский боевик", "57"),
SearchFilter("сёнэн", "61"), SearchFilter("сверхъестественное", "58"),
SearchFilter("сёнэн-ай", "62"), SearchFilter("сёдзё", "59"),
SearchFilter("спорт", "63"), SearchFilter("сёдзё-ай", "60"),
SearchFilter("сэйнэн", "64"), SearchFilter("сёнэн", "61"),
SearchFilter("трагедия", "65"), SearchFilter("сёнэн-ай", "62"),
SearchFilter("триллер", "66"), SearchFilter("спорт", "63"),
SearchFilter("ужасы", "67"), SearchFilter("сэйнэн", "64"),
SearchFilter("фантастика", "68"), SearchFilter("трагедия", "65"),
SearchFilter("фэнтези", "69"), SearchFilter("триллер", "66"),
SearchFilter("школа", "70"), SearchFilter("ужасы", "67"),
SearchFilter("эротика", "71"), SearchFilter("фантастика", "68"),
SearchFilter("этти", "72"), SearchFilter("фэнтези", "69"),
SearchFilter("юри", "73"), SearchFilter("школа", "70"),
SearchFilter("яой", "74"), SearchFilter("эротика", "71"),
SearchFilter("ёнкома", "75"), SearchFilter("этти", "72"),
SearchFilter("кодомо", "76"), SearchFilter("юри", "73"),
SearchFilter("омегаверс", "77") SearchFilter("яой", "74")
) )
} }