Unites the MangaLivre source with MangasProject (#1216)

* Unite the MangaLivre source with the mangásPROJECT source.

* Remove blank line.
This commit is contained in:
Alessandro Jean 2019-06-29 12:33:13 -03:00 committed by Eugene
parent 30cb878aa0
commit 1d89d9f3ee
5 changed files with 205 additions and 460 deletions

View File

@ -5,13 +5,8 @@ ext {
appName = 'Tachiyomi: Mangá Livre'
pkgNameSuffix = 'pt.mangalivre'
extClass = '.MangaLivre'
extVersionCode = 3
extVersionCode = 4
libVersion = '1.2'
}
dependencies {
compileOnly 'com.google.code.gson:gson:2.8.2'
compileOnly 'com.github.salomonbrys.kotson:kotson:2.5.0'
}
apply from: "$rootDir/common.gradle"

View File

@ -1,25 +1,9 @@
package eu.kanade.tachiyomi.extension.pt.mangalivre
import com.github.salomonbrys.kotson.nullString
import com.github.salomonbrys.kotson.obj
import com.google.gson.JsonObject
import com.google.gson.JsonParser
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.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import java.lang.Exception
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit
class MangaLivre : HttpSource() {
override val name = "MangaLivre"
@ -30,273 +14,18 @@ class MangaLivre : HttpSource() {
override val supportsLatest = true
// Sometimes the site is slow.
override val client =
network.client.newBuilder()
.connectTimeout(1, TimeUnit.MINUTES)
.readTimeout(1, TimeUnit.MINUTES)
.writeTimeout(1, TimeUnit.MINUTES)
.build()
private val catalogHeaders = Headers.Builder().apply {
add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36")
add("Host", "mangalivre.com")
// The API doesn't return the result if this header isn't sent.
add("X-Requested-With", "XMLHttpRequest")
}.build()
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/home/most_read?page=$page", catalogHeaders)
}
override fun popularMangaParse(response: Response): MangasPage {
val result = jsonParser.parse(response.body()!!.string()).obj
// If "most_read" have boolean false value, then it doesn't have next page.
if (!result["most_read"]!!.isJsonArray)
return MangasPage(emptyList(), false)
val popularMangas = result.getAsJsonArray("most_read")?.map {
popularMangaItemParse(it.obj)
}
val hasNextPage = response.request().url().queryParameter("page")!!.toInt() < 10
if (popularMangas != null)
return MangasPage(popularMangas, hasNextPage)
return MangasPage(emptyList(), false)
}
private fun popularMangaItemParse(obj: JsonObject) = SManga.create().apply {
title = obj["serie_name"].nullString ?: ""
thumbnail_url = obj["cover"].nullString
url = obj["link"].nullString ?: ""
}
override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/home/releases?page=$page", catalogHeaders)
}
override fun latestUpdatesParse(response: Response): MangasPage {
if (response.code() == 500)
return MangasPage(emptyList(), false)
val result = jsonParser.parse(response.body()!!.string()).obj
val latestMangas = result.getAsJsonArray("releases")?.map {
latestMangaItemParse(it.obj)
}
val hasNextPage = response.request().url().queryParameter("page")!!.toInt() < 5
if (latestMangas != null)
return MangasPage(latestMangas, hasNextPage)
return MangasPage(emptyList(), false)
}
private fun latestMangaItemParse(obj: JsonObject) = SManga.create().apply {
title = obj["name"].nullString ?: ""
thumbnail_url = obj["image"].nullString
url = obj["link"].nullString ?: ""
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val form = FormBody.Builder().apply {
add("search", query)
}
return POST("$baseUrl/lib/search/series.json", catalogHeaders, form.build())
}
override fun searchMangaParse(response: Response): MangasPage {
val result = jsonParser.parse(response.body()!!.string()).obj
// If "series" have boolean false value, then it doesn't have results.
if (!result["series"]!!.isJsonArray)
return MangasPage(emptyList(), false)
val searchMangas = result.getAsJsonArray("series")?.map {
searchMangaItemParse(it.obj)
}
if (searchMangas != null)
return MangasPage(searchMangas, false)
return MangasPage(emptyList(), false)
}
private fun searchMangaItemParse(obj: JsonObject) = SManga.create().apply {
title = obj["name"].nullString ?: ""
thumbnail_url = obj["cover"].nullString
url = obj["link"].nullString ?: ""
author = obj["author"].nullString
artist = obj["artist"].nullString
}
override fun mangaDetailsParse(response: Response): SManga {
val document = response.asJsoup()
val isCompleted = document.select("div#series-data span.series-author i.complete-series").first() != null
val cGenre = document.select("div#series-data ul.tags li").joinToString { it!!.text() }
val seriesAuthor = document.select("div#series-data span.series-author").text()
.substringAfter("Completo").substringBefore("+")
val authors = seriesAuthor.split("&")
.map { it.trim() }
val cAuthor = authors.filter { !it.contains("(Arte)") }
.map { it.split(", ").reversed().joinToString(" ") }
val cArtist = authors.filter { it.contains("(Arte)") }
.map { it.replace("\\(Arte\\)".toRegex(), "").trim() }
.map { it.split(", ").reversed().joinToString(" ") }
// Check if the manga was removed by the publisher.
var seriesBlocked = document.select("div.series-blocked-img").first()
val cStatus = when {
seriesBlocked == null && isCompleted -> SManga.COMPLETED
seriesBlocked == null && !isCompleted -> SManga.ONGOING
else -> SManga.LICENSED
}
return SManga.create().apply {
genre = cGenre
status = cStatus
description = document.select("div#series-data span.series-desc").first()?.text()
author = cAuthor.joinToString("; ")
artist = if (cArtist.isEmpty()) cAuthor.joinToString("; ") else cArtist.joinToString("; ")
}
}
// Need to override because the chapter API is paginated.
// Adapted from:
// https://stackoverflow.com/questions/35254323/rxjs-observable-pagination
// https://stackoverflow.com/questions/40529232/angular-2-http-observables-and-recursive-requests
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return if (manga.status != SManga.LICENSED) {
fetchChapterList(manga, 1)
} else {
Observable.error(Exception("Licensed - No chapters to show"))
}
}
private fun fetchChapterList(manga: SManga, page: Int,
pastChapters: List<SChapter> = emptyList()): Observable<List<SChapter>> {
val chapters = pastChapters.toMutableList()
return fetchChapterListPage(manga, page)
.flatMap {
chapters += it
if (it.isEmpty()) {
Observable.just(chapters)
} else {
fetchChapterList(manga, page + 1, chapters)
}
}
}
private fun fetchChapterListPage(manga: SManga, page: Int): Observable<List<SChapter>> {
return client.newCall(chapterListRequest(manga, page))
.asObservableSuccess()
.map { response ->
chapterListParse(response)
}
}
override fun chapterListRequest(manga: SManga): Request {
return chapterListRequest(manga, 1)
}
private fun chapterListRequest(manga: SManga, page: Int): Request {
val id = manga.url.substringAfterLast("/")
return GET("$baseUrl/series/chapters_list.json?page=$page&id_serie=$id", catalogHeaders)
}
override fun chapterListParse(response: Response): List<SChapter> {
val result = jsonParser.parse(response.body()!!.string()).obj
if (!result["chapters"]!!.isJsonArray)
return emptyList()
return result.getAsJsonArray("chapters")?.map {
chapterListItemParse(it.obj)
} ?: emptyList()
}
private fun chapterListItemParse(obj: JsonObject): SChapter {
val scan = obj["releases"]!!.asJsonObject!!.entrySet().first().value.obj
val cName = obj["chapter_name"]!!.asString
val scanlators = scan["scanlators"]!!.asJsonArray
.joinToString { it.asJsonObject["name"].asString }
return SChapter.create().apply {
name = "Cap. ${obj["number"]!!.asString}" + (if (cName == "") "" else " - $cName")
date_upload = parseChapterDate(obj["date_created"].asString.substring(0, 10))
scanlator = scanlators
url = scan["link"]!!.nullString ?: ""
chapter_number = obj["number"]!!.asString.toFloatOrNull() ?: "1".toFloat()
}
}
private fun parseChapterDate(date: String?) : Long {
return try {
SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH).parse(date).time
} catch (e: ParseException) {
0L
}
}
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
return client.newCall(pageListRequest(chapter))
.asObservableSuccess()
.flatMap { response ->
val token = getReaderToken(response)
return@flatMap if (token == "")
Observable.error(Exception("Licensed - No chapter to show"))
else fetchPageListApi(chapter, token)
}
}
private fun fetchPageListApi(chapter: SChapter, token: String): Observable<List<Page>> {
val id = "\\/(\\d+)\\/capitulo".toRegex().find(chapter.url)?.groupValues?.get(1) ?: ""
return client.newCall(pageListApiRequest(id, token))
.asObservableSuccess()
.map { response ->
pageListParse(response)
}
}
private fun pageListApiRequest(id: String, token: String): Request {
return GET("$baseUrl/leitor/pages/$id.json?key=$token", catalogHeaders)
}
override fun pageListParse(response: Response): List<Page> {
val result = jsonParser.parse(response.body()!!.string()).obj
return result["images"]!!.asJsonArray
.mapIndexed { i, obj ->
Page(i, obj.asString, obj.asString)
}
}
override fun fetchImageUrl(page: Page): Observable<String> {
return Observable.just(page.imageUrl!!)
}
override fun imageUrlParse(response: Response): String = ""
private fun getReaderToken(response: Response): String {
val document = response.asJsoup()
// The pages API needs the token provided in the reader script.
val scriptSrc = document.select("script[src*=\"reader.min.js\"]")?.first()?.attr("src") ?: ""
return "token=(.*)&id".toRegex().find(scriptSrc)?.groupValues?.get(1) ?: ""
}
override fun popularMangaRequest(page: Int) = throw Exception(NEED_MIGRATION)
override fun popularMangaParse(response: Response) = throw Exception(NEED_MIGRATION)
override fun latestUpdatesRequest(page: Int) = throw Exception(NEED_MIGRATION)
override fun latestUpdatesParse(response: Response) = throw Exception(NEED_MIGRATION)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw Exception(NEED_MIGRATION)
override fun searchMangaParse(response: Response) = throw Exception(NEED_MIGRATION)
override fun mangaDetailsParse(response: Response) = throw Exception(NEED_MIGRATION)
override fun chapterListParse(response: Response) = throw Exception(NEED_MIGRATION)
override fun pageListParse(response: Response) = throw Exception(NEED_MIGRATION)
override fun imageUrlParse(response: Response) = throw Exception(NEED_MIGRATION)
companion object {
val jsonParser by lazy {
JsonParser()
}
private const val NEED_MIGRATION = "Catálogo incorporado na nova versão da extensão mangásPROJECT."
}
}

View File

@ -4,8 +4,8 @@ apply plugin: 'kotlin-android'
ext {
appName = 'Tachiyomi: mangásPROJECT'
pkgNameSuffix = 'pt.mangasproject'
extClass = '.MangasProject'
extVersionCode = 3
extClass = '.MangasProjectFactory'
extVersionCode = 4
libVersion = '1.2'
}

View File

@ -1,19 +1,17 @@
package eu.kanade.tachiyomi.extension.pt.mangasproject
import com.github.salomonbrys.kotson.nullString
import com.github.salomonbrys.kotson.array
import com.github.salomonbrys.kotson.obj
import com.github.salomonbrys.kotson.string
import com.google.gson.JsonObject
import com.google.gson.JsonParser
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.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.Request
import okhttp3.Response
import okhttp3.*
import org.jsoup.nodes.Element
import rx.Observable
import java.lang.Exception
import java.text.ParseException
@ -21,222 +19,217 @@ import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit
class MangasProject : HttpSource() {
override val name = "mangásPROJECT"
override val baseUrl = "https://leitor.net"
abstract class MangasProject(override val name: String,
override val baseUrl: String) : HttpSource() {
override val lang = "pt"
override val supportsLatest = true
// Sometimes the site is slow.
override val client =
network.client.newBuilder()
.connectTimeout(1, TimeUnit.MINUTES)
.readTimeout(1, TimeUnit.MINUTES)
.writeTimeout(1, TimeUnit.MINUTES)
.build()
override val client = network.client.newBuilder()
.connectTimeout(1, TimeUnit.MINUTES)
.readTimeout(1, TimeUnit.MINUTES)
.writeTimeout(1, TimeUnit.MINUTES)
.addInterceptor { pageListIntercept(it) }
.build()
private val catalogHeaders = Headers.Builder().apply {
add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36")
add("Host", "leitor.net")
// The API doesn't return the result if this header isn't sent.
add("X-Requested-With", "XMLHttpRequest")
}.build()
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("User-Agent", USER_AGENT)
.add("Referer", baseUrl)
.add("X-Requested-With", "XMLHttpRequest")
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/home/most_read?page=$page", catalogHeaders)
return GET("$baseUrl/home/most_read?page=$page", headers)
}
override fun popularMangaParse(response: Response): MangasPage {
val result = jsonParser.parse(response.body()!!.string()).obj
val result = response.asJsonObject()
// If "most_read" have boolean false value, then it doesn't have next page.
if (!result["most_read"]!!.isJsonArray)
return MangasPage(emptyList(), false)
val popularMangas = result.getAsJsonArray("most_read")?.map {
popularMangaItemParse(it.obj)
}
val popularMangas = result["most_read"].array
.map { popularMangaItemParse(it.obj) }
val hasNextPage = response.request().url().queryParameter("page")!!.toInt() < 10
val page = response.request().url().queryParameter("page")!!.toInt()
if (popularMangas != null)
return MangasPage(popularMangas, hasNextPage)
return MangasPage(emptyList(), false)
return MangasPage(popularMangas, page < 10)
}
private fun popularMangaItemParse(obj: JsonObject) = SManga.create().apply {
title = obj["serie_name"].nullString ?: ""
thumbnail_url = obj["cover"].nullString
url = obj["link"].nullString ?: ""
title = obj["serie_name"].string
thumbnail_url = obj["cover"].string
url = obj["link"].string
}
override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/home/releases?page=$page", catalogHeaders)
return GET("$baseUrl/home/releases?page=$page", headers)
}
override fun latestUpdatesParse(response: Response): MangasPage {
if (response.code() == 500)
return MangasPage(emptyList(), false)
val result = jsonParser.parse(response.body()!!.string()).obj
val result = response.asJsonObject()
val latestMangas = result.getAsJsonArray("releases")?.map {
latestMangaItemParse(it.obj)
}
val latestMangas = result["releases"].array
.map { latestMangaItemParse(it.obj) }
val hasNextPage = response.request().url().queryParameter("page")!!.toInt() < 5
val page = response.request().url().queryParameter("page")!!.toInt()
if (latestMangas != null)
return MangasPage(latestMangas, hasNextPage)
return MangasPage(emptyList(), false)
return MangasPage(latestMangas, page < 5)
}
private fun latestMangaItemParse(obj: JsonObject) = SManga.create().apply {
title = obj["name"].nullString ?: ""
thumbnail_url = obj["image"].nullString
url = obj["link"].nullString ?: ""
title = obj["name"].string
thumbnail_url = obj["image"].string
url = obj["link"].string
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val form = FormBody.Builder().apply {
add("search", query)
}
val form = FormBody.Builder()
.add("search", query)
.build()
return POST("$baseUrl/lib/search/series.json", catalogHeaders, form.build())
return POST("$baseUrl/lib/search/series.json", headers, form)
}
override fun searchMangaParse(response: Response): MangasPage {
val result = jsonParser.parse(response.body()!!.string()).obj
val result = response.asJsonObject()
// If "series" have boolean false value, then it doesn't have results.
if (!result["series"]!!.isJsonArray)
return MangasPage(emptyList(), false)
val searchMangas = result.getAsJsonArray("series")?.map {
searchMangaItemParse(it.obj)
}
val searchMangas = result["series"].array
.map { searchMangaItemParse(it.obj) }
if (searchMangas != null)
return MangasPage(searchMangas, false)
return MangasPage(emptyList(), false)
return MangasPage(searchMangas, false)
}
private fun searchMangaItemParse(obj: JsonObject) = SManga.create().apply {
title = obj["name"].nullString ?: ""
thumbnail_url = obj["cover"].nullString
url = obj["link"].nullString ?: ""
author = obj["author"].nullString
artist = obj["artist"].nullString
title = obj["name"].string
thumbnail_url = obj["cover"].string
url = obj["link"].string
author = obj["author"].string
artist = obj["artist"].string
}
override fun mangaDetailsRequest(manga: SManga): Request {
val newHeaders = Headers.Builder()
.add("User-Agent", USER_AGENT)
.add("Referer", baseUrl)
.build()
return GET(baseUrl + manga.url, newHeaders)
}
override fun mangaDetailsParse(response: Response): SManga {
val document = response.asJsoup()
val isCompleted = document.select("div#series-data span.series-author i.complete-series").first() != null
val cGenre = document.select("div#series-data ul.tags li").joinToString { it!!.text() }
val seriesAuthor = document.select("div#series-data span.series-author").text()
.substringAfter("Completo").substringBefore("+")
val seriesData = document.select("#series-data")
val authors = seriesAuthor.split("&")
.map { it.trim() }
val cAuthor = authors.filter { !it.contains("(Arte)") }
.map { it.split(", ").reversed().joinToString(" ") }
val cArtist = authors.filter { it.contains("(Arte)") }
.map { it.replace("\\(Arte\\)".toRegex(), "").trim() }
.map { it.split(", ").reversed().joinToString(" ") }
val isCompleted = seriesData.select("span.series-author i.complete-series").first() != null
// Check if the manga was removed by the publisher.
var seriesBlocked = document.select("div.series-blocked-img").first()
val cStatus = when {
seriesBlocked == null && isCompleted -> SManga.COMPLETED
seriesBlocked == null && !isCompleted -> SManga.ONGOING
else -> SManga.LICENSED
}
val seriesBlocked = document.select("div.series-blocked-img").first()
val seriesAuthors = document.select("div#series-data span.series-author").text()
.substringAfter("Completo")
.substringBefore("+")
.split("&")
.map { it.trim() }
val seriesAuthor = seriesAuthors
.filter { !it.contains("(Arte)") }
.joinToString("; ") {
it.split(", ")
.reversed()
.joinToString(" ")
}
return SManga.create().apply {
genre = cGenre
status = cStatus
description = document.select("div#series-data span.series-desc").first()?.text()
author = cAuthor.joinToString("; ")
artist = if (cArtist.isEmpty()) cAuthor.joinToString("; ") else cArtist.joinToString("; ")
thumbnail_url = seriesData.select("div.series-img > div.cover > img").attr("src")
description = seriesData.select("span.series-desc").text()
status = parseStatus(seriesBlocked, isCompleted)
author = seriesAuthor
artist = seriesAuthors.filter { it.contains("(Arte)") }
.map { it.replace("\\(Arte\\)".toRegex(), "").trim() }
.joinToString("; ") {
it.split(", ")
.reversed()
.joinToString(" ")
}
.ifEmpty { seriesAuthor }
genre = seriesData.select("div#series-data ul.tags li")
.joinToString { it.text() }
}
}
// Need to override because the chapter API is paginated.
// Adapted from:
// https://stackoverflow.com/questions/35254323/rxjs-observable-pagination
// https://stackoverflow.com/questions/40529232/angular-2-http-observables-and-recursive-requests
private fun parseStatus(seriesBlocked: Element?, isCompleted: Boolean) = when {
seriesBlocked != null -> SManga.LICENSED
isCompleted -> SManga.COMPLETED
else -> SManga.ONGOING
}
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return if (manga.status != SManga.LICENSED) {
fetchChapterList(manga, 1)
} else {
Observable.error(Exception("Licensed - No chapters to show"))
}
}
if (manga.status != SManga.LICENSED)
return super.fetchChapterList(manga)
private fun fetchChapterList(manga: SManga, page: Int,
pastChapters: List<SChapter> = emptyList()): Observable<List<SChapter>> {
val chapters = pastChapters.toMutableList()
return fetchChapterListPage(manga, page)
.flatMap {
chapters += it
if (it.isEmpty()) {
Observable.just(chapters)
} else {
fetchChapterList(manga, page + 1, chapters)
}
}
}
private fun fetchChapterListPage(manga: SManga, page: Int): Observable<List<SChapter>> {
return client.newCall(chapterListRequest(manga, page))
.asObservableSuccess()
.map { response ->
chapterListParse(response)
}
return Observable.error(Exception("Mangá licenciado e removido pela editora."))
}
override fun chapterListRequest(manga: SManga): Request {
return chapterListRequest(manga, 1)
val id = manga.url.substringAfterLast("/")
return chapterListRequestPaginated(manga.url, id, 1)
}
private fun chapterListRequest(manga: SManga, page: Int): Request {
val id = manga.url.substringAfterLast("/")
return GET("$baseUrl/series/chapters_list.json?page=$page&id_serie=$id", catalogHeaders)
private fun chapterListRequestPaginated(mangaUrl: String, id: String, page: Int): Request {
val newHeaders = headersBuilder()
.set("Referer", baseUrl + mangaUrl)
.build()
return GET("$baseUrl/series/chapters_list.json?page=$page&id_serie=$id", newHeaders)
}
override fun chapterListParse(response: Response): List<SChapter> {
val result = jsonParser.parse(response.body()!!.string()).obj
var result = response.asJsonObject()
if (!result["chapters"]!!.isJsonArray)
return emptyList()
return result.getAsJsonArray("chapters")?.map {
chapterListItemParse(it.obj)
} ?: emptyList()
val mangaUrl = response.request().header("Referer")!!
val mangaId = mangaUrl.substringAfterLast("/")
var page = 1
val chapters = mutableListOf<SChapter>()
while (result["chapters"]!!.isJsonArray) {
chapters += result["chapters"].array
.map { chapterListItemParse(it.obj) }
.toMutableList()
val newRequest = chapterListRequestPaginated(mangaUrl, mangaId, ++page)
result = client.newCall(newRequest).execute().asJsonObject()
}
return chapters
}
private fun chapterListItemParse(obj: JsonObject): SChapter {
val scan = obj["releases"]!!.asJsonObject!!.entrySet().first().value.obj
val cName = obj["chapter_name"]!!.asString
val scanlators = scan["scanlators"]!!.asJsonArray
.joinToString { it.asJsonObject["name"].asString }
val scan = obj["releases"].obj.entrySet().first().value.obj
val chapterName = obj["chapter_name"]!!.string
return SChapter.create().apply {
name = "Cap. ${obj["number"]!!.asString}" + (if (cName == "") "" else " - $cName")
date_upload = parseChapterDate(obj["date_created"].asString.substring(0, 10))
scanlator = scanlators
url = scan["link"]!!.nullString ?: ""
chapter_number = obj["number"]!!.asString.toFloatOrNull() ?: "1".toFloat()
name = "Cap. ${obj["number"].string}" + (if (chapterName == "") "" else " - $chapterName")
date_upload = parseChapterDate(obj["date_created"].string.substringBefore("T"))
scanlator = scan["scanlators"]!!.array
.joinToString { it.obj["name"].string }
url = scan["link"].string
chapter_number = obj["number"].string.toFloatOrNull() ?: 0f
}
}
@ -248,37 +241,52 @@ class MangasProject : HttpSource() {
}
}
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
return client.newCall(pageListRequest(chapter))
.asObservableSuccess()
.flatMap { response ->
val token = getReaderToken(response)
return@flatMap if (token == "")
Observable.error(Exception("Licensed - No chapter to show"))
else fetchPageListApi(chapter, token)
}
override fun pageListRequest(chapter: SChapter): Request {
val newHeaders = Headers.Builder()
.add("User-Agent", USER_AGENT)
.add("Referer", baseUrl + chapter.url)
.build()
return GET(baseUrl + chapter.url, newHeaders)
}
private fun fetchPageListApi(chapter: SChapter, token: String): Observable<List<Page>> {
val id = "\\/(\\d+)\\/capitulo".toRegex().find(chapter.url)?.groupValues?.get(1) ?: ""
return client.newCall(pageListApiRequest(id, token))
.asObservableSuccess()
.map { response ->
pageListParse(response)
}
private fun pageListApiRequest(chapterUrl: String, token: String): Request {
val newHeaders = headersBuilder()
.set("Referer", chapterUrl)
.build()
val id = chapterUrl
.substringBeforeLast("/")
.substringAfterLast("/")
return GET("$baseUrl/leitor/pages/$id.json?key=$token", newHeaders)
}
private fun pageListApiRequest(id: String, token: String): Request {
return GET("$baseUrl/leitor/pages/$id.json?key=$token", catalogHeaders)
private fun pageListIntercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val result = chain.proceed(request)
if (!request.url().toString().contains("capitulo-"))
return result
val document = result.asJsoup()
val readerSrc = document.select("script[src*=\"reader.min.js\"]")
?.attr("src") ?: ""
val token = TOKEN_REGEX.find(readerSrc)?.groupValues?.get(1) ?: ""
if (token.isEmpty())
throw Exception("Mangá licenciado e removido pela editora.")
return chain.proceed(pageListApiRequest(request.url().toString(), token))
}
override fun pageListParse(response: Response): List<Page> {
val result = jsonParser.parse(response.body()!!.string()).obj
val result = response.asJsonObject()
return result["images"]!!.asJsonArray
.mapIndexed { i, obj ->
Page(i, obj.asString, obj.asString)
}
return result["images"].array
.filter { it.string.startsWith("http") }
.mapIndexed { i, obj -> Page(i, "", obj.string)}
}
override fun fetchImageUrl(page: Page): Observable<String> {
@ -287,16 +295,13 @@ class MangasProject : HttpSource() {
override fun imageUrlParse(response: Response): String = ""
private fun getReaderToken(response: Response): String {
val document = response.asJsoup()
// The pages API needs the token provided in the reader script.
val scriptSrc = document.select("script[src*=\"reader.min.js\"]")?.first()?.attr("src") ?: ""
return "token=(.*)&id".toRegex().find(scriptSrc)?.groupValues?.get(1) ?: ""
}
private fun Response.asJsonObject(): JsonObject = JSON_PARSER.parse(body()!!.string()).obj
companion object {
val jsonParser by lazy {
JsonParser()
}
private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
private val TOKEN_REGEX = "token=(.*)&id".toRegex()
private val JSON_PARSER by lazy { JsonParser() }
}
}

View File

@ -0,0 +1,16 @@
package eu.kanade.tachiyomi.extension.pt.mangasproject
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
class MangasProjectFactory : SourceFactory {
override fun createSources(): List<Source> = getAllSources()
}
class MangasProjectOriginal : MangasProject("mangásPROJECT", "https://leitor.net")
class MangaLivre : MangasProject("MangaLivre", "https://mangalivre.com")
fun getAllSources(): List<Source> = listOf(
MangasProjectOriginal(),
MangaLivre()
)