Add kotlinx.serialization to more sources (#7398)

* Replace Gson in Foolslide, Wpmangastream, Gigaviewer and Bilibili.

* Replace Gson in Wpmangareader and Cubari.

* Replace Gson in MangaAdventure and NaniScans.

* Fix missing pages at Wpmangastream (closes #7395).

* Replace Gson in Webtoons and WebtoonsTranslate.
This commit is contained in:
Alessandro Jean 2021-06-03 12:55:26 -03:00 committed by GitHub
parent d6365b5910
commit 6d144464c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 626 additions and 531 deletions

View File

@ -1,20 +1,17 @@
package eu.kanade.tachiyomi.extension.pt.silencescan package eu.kanade.tachiyomi.extension.pt.silencescan
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.multisrc.wpmangastream.WPMangaStream import eu.kanade.tachiyomi.multisrc.wpmangastream.WPMangaStream
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import com.github.salomonbrys.kotson.array
import com.github.salomonbrys.kotson.obj
import com.github.salomonbrys.kotson.string
import com.google.gson.JsonParser
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import okhttp3.OkHttpClient
class SilenceScan : WPMangaStream( class SilenceScan : WPMangaStream(
"Silence Scan", "Silence Scan",
@ -29,31 +26,33 @@ class SilenceScan : WPMangaStream(
.addNetworkInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS)) .addNetworkInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS))
.build() .build()
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { private val json: Json by injectLazy()
val infoEl = document.select("div.bigcontent, div.animefull").first()
author = infoEl.select("b:contains(Autor) + span").text() override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
artist = infoEl.select("b:contains(Artista) + span").text() val infoEl = document.select("div.bigcontent, div.animefull, div.main-info").first()
author = infoEl.select("div.imptdt:contains(Autor) i").text()
artist = infoEl.select("div.imptdt:contains(Artista) + i").text()
status = parseStatus(infoEl.select("div.imptdt:contains(Status) i").text()) status = parseStatus(infoEl.select("div.imptdt:contains(Status) i").text())
description = infoEl.select("h2:contains(Sinopse) + div p").joinToString("\n") { it.text() } description = infoEl.select("h2:contains(Sinopse) + div p:not([class])").joinToString("\n") { it.text() }
thumbnail_url = infoEl.select("div.thumb img").imgAttr() thumbnail_url = infoEl.select("div.thumb img").imgAttr()
val genres = infoEl.select("b:contains(Gêneros) + span a") val genres = infoEl.select("span.mgen a[rel]")
.map { element -> element.text().toLowerCase() } .map { element -> element.text() }
.toMutableSet() .toMutableSet()
// add series type(manga/manhwa/manhua/other) thinggy to genre // add series type(manga/manhwa/manhua/other) thinggy to genre
document.select(seriesTypeSelector).firstOrNull()?.ownText()?.let { document.select(seriesTypeSelector).firstOrNull()?.ownText()?.let {
if (it.isEmpty().not() && genres.contains(it).not()) { if (it.isEmpty().not() && genres.contains(it).not()) {
genres.add(it.toLowerCase()) genres.add(it)
} }
} }
genre = genres.toList().map { it.capitalize() }.joinToString(", ") genre = genres.toList().joinToString()
// add alternative name to manga description // add alternative name to manga description
document.select(altNameSelector).firstOrNull()?.ownText()?.let { document.select(altNameSelector).firstOrNull()?.ownText()?.let {
if (it.isEmpty().not() && it !="N/A" && it != "-") { if (it.isEmpty().not() && it != "N/A" && it != "-") {
description += when { description += when {
description!!.isEmpty() -> altName + it description!!.isEmpty() -> altName + it
else -> "\n\n$altName" + it else -> "\n\n$altName" + it
@ -63,7 +62,14 @@ class SilenceScan : WPMangaStream(
} }
override val seriesTypeSelector = ".imptdt:contains(Tipo) a, a[href*=type\\=]" override val seriesTypeSelector = ".imptdt:contains(Tipo) a, a[href*=type\\=]"
override val altNameSelector = ".wd-full:contains(Alt) span" override val altName: String = "Nome alternativo: "
override fun parseStatus(element: String?): Int = when {
element == null -> SManga.UNKNOWN
element.contains("em andamento", true) -> SManga.ONGOING
element.contains("completo", true) -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
name = element.select("span.chapternum").text() name = element.select("span.chapternum").text()
@ -73,24 +79,6 @@ class SilenceScan : WPMangaStream(
setUrlWithoutDomain(element.select("div.eph-num > a").attr("href")) setUrlWithoutDomain(element.select("div.eph-num > a").attr("href"))
} }
override fun pageListParse(document: Document): List<Page> {
val chapterObj = document.select("script:containsData(ts_reader)").first()
.data()
.substringAfter("run(")
.substringBeforeLast(");")
.let { JsonParser.parseString(it) }
.obj
if (chapterObj["sources"].array.size() == 0) {
return emptyList()
}
val firstServerAvailable = chapterObj["sources"].array[0].obj
return firstServerAvailable["images"].array
.mapIndexed { i, pageUrl -> Page(i, "", pageUrl.string) }
}
override fun getGenreList(): List<Genre> = listOf( override fun getGenreList(): List<Genre> = listOf(
Genre("4-koma", "4-koma"), Genre("4-koma", "4-koma"),
Genre("Ação", "acao"), Genre("Ação", "acao"),
@ -126,4 +114,8 @@ class SilenceScan : WPMangaStream(
Genre("Violência sexual", "violencia-sexual"), Genre("Violência sexual", "violencia-sexual"),
Genre("Yuri", "yuri") Genre("Yuri", "yuri")
) )
companion object {
private val PORTUGUESE_LOCALE = Locale("pt", "BR")
}
} }

View File

@ -1,7 +1,5 @@
package eu.kanade.tachiyomi.multisrc.foolslide package eu.kanade.tachiyomi.multisrc.foolslide
import com.github.salomonbrys.kotson.get
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.POST
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
@ -10,11 +8,16 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
@ -33,6 +36,8 @@ abstract class FoolSlide(
override val supportsLatest = true override val supportsLatest = true
private val json: Json by injectLazy()
override fun popularMangaSelector() = "div.group" override fun popularMangaSelector() = "div.group"
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
@ -166,7 +171,7 @@ abstract class FoolSlide(
} }
open fun parseChapterDate(date: String): Long? { open fun parseChapterDate(date: String): Long? {
val lcDate = date.toLowerCase() val lcDate = date.toLowerCase(Locale.ROOT)
if (lcDate.endsWith(" ago")) if (lcDate.endsWith(" ago"))
parseRelativeDate(lcDate)?.let { return it } parseRelativeDate(lcDate)?.let { return it }
@ -272,18 +277,17 @@ abstract class FoolSlide(
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val doc = document.toString() val doc = document.toString()
val jsonstr = doc.substringAfter("var pages = ").substringBefore(";") val jsonStr = doc.substringAfter("var pages = ").substringBefore(";")
val json = JsonParser().parse(jsonstr).asJsonArray val pages = json.parseToJsonElement(jsonStr).jsonArray
val pages = mutableListOf<Page>()
json.forEach { return pages.mapIndexed { i, jsonEl ->
// Create dummy element to resolve relative URL // Create dummy element to resolve relative URL
val absUrl = document.createElement("a") val absUrl = document.createElement("a")
.attr("href", it["url"].asString) .attr("href", jsonEl.jsonObject["url"]!!.jsonPrimitive.content)
.absUrl("href") .absUrl("href")
pages.add(Page(pages.size, "", absUrl)) Page(i, "", absUrl)
} }
return pages
} }
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used")

View File

@ -10,7 +10,7 @@ class FoolSlideGenerator : ThemeSourceGenerator {
override val themeClass = "FoolSlide" override val themeClass = "FoolSlide"
override val baseVersionCode: Int = 1 override val baseVersionCode: Int = 2
override val sources = listOf( override val sources = listOf(
SingleLang("The Cat Scans", "https://reader2.thecatscans.com/", "en"), SingleLang("The Cat Scans", "https://reader2.thecatscans.com/", "en"),

View File

@ -4,11 +4,6 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Rect import android.graphics.Rect
import com.github.salomonbrys.kotson.get
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.GET
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
@ -18,6 +13,10 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Interceptor import okhttp3.Interceptor
@ -28,6 +27,7 @@ import okhttp3.ResponseBody.Companion.toResponseBody
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.InputStream import java.io.InputStream
import java.text.ParseException import java.text.ParseException
@ -57,6 +57,8 @@ abstract class GigaViewer(
.add("Origin", baseUrl) .add("Origin", baseUrl)
.add("Referer", baseUrl) .add("Referer", baseUrl)
private val json: Json by injectLazy()
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/series", headers) override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/series", headers)
override fun popularMangaSelector(): String = "ul.series-list li a" override fun popularMangaSelector(): String = "ul.series-list li a"
@ -143,9 +145,12 @@ abstract class GigaViewer(
var result = client.newCall(request).execute() var result = client.newCall(request).execute()
while (result.code != 404) { while (result.code != 404) {
val json = result.asJsonObject() val jsonResult = json.parseToJsonElement(result.body!!.string()).jsonObject
readMoreEndpoint = json["nextUrl"].string readMoreEndpoint = jsonResult["nextUrl"]!!.jsonPrimitive.content
val tempDocument = Jsoup.parse(json["html"].string, response.request.url.toString()) val tempDocument = Jsoup.parse(
jsonResult["html"]!!.jsonPrimitive.content,
response.request.url.toString()
)
chapters += tempDocument chapters += tempDocument
.select("ul.series-episode-list " + chapterListSelector()) .select("ul.series-episode-list " + chapterListSelector())
@ -177,16 +182,16 @@ abstract class GigaViewer(
} }
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val episodeJson = document.select("script#episode-json") val episode = document.select("script#episode-json")
.attr("data-value") .attr("data-value")
.let { JsonParser.parseString(it).obj } .let { json.decodeFromString<GigaViewerEpisodeDto>(it) }
return episodeJson["readableProduct"]["pageStructure"]["pages"].asJsonArray return episode.readableProduct.pageStructure.pages
.filter { it["type"].string == "main" } .filter { it.type == "main" }
.mapIndexed { i, pageObj -> .mapIndexed { i, page ->
val imageUrl = pageObj["src"].string.toHttpUrlOrNull()!!.newBuilder() val imageUrl = page.src.toHttpUrlOrNull()!!.newBuilder()
.addQueryParameter("width", pageObj["width"].string) .addQueryParameter("width", page.width.toString())
.addQueryParameter("height", pageObj["height"].string) .addQueryParameter("height", page.height.toString())
.toString() .toString()
Page(i, document.location(), imageUrl) Page(i, document.location(), imageUrl)
} }
@ -280,8 +285,6 @@ abstract class GigaViewer(
} }
} }
private fun Response.asJsonObject(): JsonObject = JsonParser.parseString(body!!.string()).obj
companion object { companion object {
private val DATE_PARSER by lazy { SimpleDateFormat("yyyy/MM/dd", Locale.ENGLISH) } private val DATE_PARSER by lazy { SimpleDateFormat("yyyy/MM/dd", Locale.ENGLISH) }

View File

@ -0,0 +1,26 @@
package eu.kanade.tachiyomi.multisrc.gigaviewer
import kotlinx.serialization.Serializable
@Serializable
data class GigaViewerEpisodeDto(
val readableProduct: GigaViewerReadableProduct
)
@Serializable
data class GigaViewerReadableProduct(
val pageStructure: GigaViewerPageStructure
)
@Serializable
data class GigaViewerPageStructure(
val pages: List<GigaViewerPage> = emptyList()
)
@Serializable
data class GigaViewerPage(
val height: Int = 0,
val src: String = "",
val type: String = "",
val width: Int = 0
)

View File

@ -9,7 +9,7 @@ class GigaViewerGenerator : ThemeSourceGenerator {
override val themeClass = "GigaViewer" override val themeClass = "GigaViewer"
override val baseVersionCode: Int = 1 override val baseVersionCode: Int = 2
override val sources = listOf( override val sources = listOf(
SingleLang("Comic Gardo", "https://comic-gardo.com", "ja"), SingleLang("Comic Gardo", "https://comic-gardo.com", "ja"),

View File

@ -12,12 +12,17 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers import okhttp3.Headers
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.json.JSONArray
import org.json.JSONObject
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@ -55,6 +60,8 @@ abstract class MangAdventure(
add("Referer", baseUrl) add("Referer", baseUrl)
} }
private val json: Json by injectLazy()
override fun latestUpdatesRequest(page: Int) = override fun latestUpdatesRequest(page: Int) =
GET("$apiUrl/releases/", headers) GET("$apiUrl/releases/", headers)
@ -100,58 +107,57 @@ abstract class MangAdventure(
} }
override fun latestUpdatesParse(response: Response) = override fun latestUpdatesParse(response: Response) =
JSONArray(response.asString()).run { json.parseToJsonElement(response.asString()).jsonArray.run {
MangasPage( MangasPage(
(0 until length()).map { map {
val obj = getJSONObject(it) val obj = it.jsonObject
SManga.create().apply { SManga.create().apply {
url = obj.getString("url") url = obj["url"]!!.jsonPrimitive.content
title = obj.getString("title") title = obj["title"]!!.jsonPrimitive.content
thumbnail_url = obj.getString("cover") thumbnail_url = obj["cover"]!!.jsonPrimitive.content
// A bit of a hack to sort by date
description = httpDateToTimestamp( description = httpDateToTimestamp(
obj.getJSONObject("latest_chapter").getString("date") obj["latest_chapter"]!!.jsonObject["date"]!!.jsonPrimitive.content
).toString() ).toString()
} }
}.sortedByDescending(SManga::description), }
.sortedByDescending(SManga::description),
false false
) )
} }
override fun chapterListParse(response: Response) = override fun chapterListParse(response: Response) =
JSONObject(response.asString()).getJSONObject("volumes").run { json.parseToJsonElement(response.asString()).jsonObject["volumes"]!!.jsonObject.entries
keys().asSequence().flatMap { vol -> .reversed()
val chapters = getJSONObject(vol) .flatMap { vol ->
chapters.keys().asSequence().map { ch -> vol.value.jsonObject.entries.map { ch ->
SChapter.create().fromJSON( SChapter.create().fromJSON(
chapters.getJSONObject(ch).also { ch.value.jsonObject.toMutableMap().let {
it.put("volume", vol) it["volume"] = JsonPrimitive(vol.key)
it.put("chapter", ch) it["chapter"] = JsonPrimitive(ch.key)
JsonObject(it)
} }
) )
} }
}.toList().reversed() }
}
override fun mangaDetailsParse(response: Response) = override fun mangaDetailsParse(response: Response) =
SManga.create().fromJSON(JSONObject(response.asString())) SManga.create().fromJSON(json.parseToJsonElement(response.asString()).jsonObject)
override fun pageListParse(response: Response) = override fun pageListParse(response: Response) =
JSONObject(response.asString()).run { json.parseToJsonElement(response.asString()).jsonObject.run {
val url = getString("url") val url = get("url")!!.jsonPrimitive.content
val root = getString("pages_root") val root = get("pages_root")!!.jsonPrimitive.content
val arr = getJSONArray("pages_list")
(0 until arr.length()).map { get("pages_list")!!.jsonArray.mapIndexed { i, jsonEl ->
Page(it, "$url${it + 1}", "$root${arr.getString(it)}") Page(i, "$url${i + 1}", "$root${jsonEl.jsonPrimitive.content}")
} }
} }
override fun searchMangaParse(response: Response) = override fun searchMangaParse(response: Response) =
JSONArray(response.asString()).run { json.parseToJsonElement(response.asString()).jsonArray.run {
MangasPage( MangasPage(
(0 until length()).map { map { SManga.create().fromJSON(it.jsonObject) },
SManga.create().fromJSON(getJSONObject(it))
}.sortedBy(SManga::title),
false false
) )
} }

View File

@ -3,9 +3,15 @@ package eu.kanade.tachiyomi.multisrc.mangadventure
import android.net.Uri import android.net.Uri
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.intOrNull
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Response import okhttp3.Response
import org.json.JSONArray
import org.json.JSONObject
import java.text.DecimalFormat import java.text.DecimalFormat
/** Returns the body of a response as a `String`. */ /** Returns the body of a response as a `String`. */
@ -22,16 +28,14 @@ fun Number.format(fmt: String): String = DecimalFormat(fmt).format(this)
/** /**
* Joins each value of a given [field] of the array using [sep]. * Joins each value of a given [field] of the array using [sep].
* *
* @param field The index of a [JSONArray]. * @param field The index of a [JsonArray].
* When its type is [String], it is treated as the key of a [JSONObject]. * When its type is [String], it is treated as the key of a [JsonObject].
* @param sep The separator used to join the array. * @param sep The separator used to join the array.
* @return The joined string, or `null` if the array is empty. * @return The joined string, or `null` if the array is empty.
*/ */
fun JSONArray.joinField(field: Int, sep: String = ", ") = fun JsonArray.joinField(field: Int, sep: String = ", ") =
length().takeIf { it != 0 }?.run { size.takeIf { it != 0 }?.run {
(0 until this).joinToString(sep) { joinToString(sep) { it.jsonArray[field].jsonPrimitive.content }
getJSONArray(it).getString(field)
}
} }
/** /**
@ -41,11 +45,9 @@ fun JSONArray.joinField(field: Int, sep: String = ", ") =
* @param sep The separator used to join the array. * @param sep The separator used to join the array.
* @return The joined string, or `null` if the array is empty. * @return The joined string, or `null` if the array is empty.
*/ */
fun JSONArray.joinField(field: String, sep: String = ", ") = fun JsonArray.joinField(field: String, sep: String = ", ") =
length().takeIf { it != 0 }?.run { size.takeIf { it != 0 }?.run {
(0 until this).joinToString(sep) { joinToString(sep) { it.jsonObject[field]!!.jsonPrimitive.content }
getJSONObject(it).getString(field)
}
} }
/** The slug of a manga. */ /** The slug of a manga. */
@ -53,19 +55,19 @@ val SManga.slug: String
get() = Uri.parse(url).lastPathSegment!! get() = Uri.parse(url).lastPathSegment!!
/** /**
* Creates a [SManga] by parsing a [JSONObject]. * Creates a [SManga] by parsing a [JsonObject].
* *
* @param obj The object containing the manga info. * @param obj The object containing the manga info.
*/ */
fun SManga.fromJSON(obj: JSONObject) = apply { fun SManga.fromJSON(obj: JsonObject) = apply {
url = obj.getString("url") url = obj["url"]!!.jsonPrimitive.content
title = obj.getString("title") title = obj["title"]!!.jsonPrimitive.content
description = obj.getString("description") description = obj["description"]!!.jsonPrimitive.content
thumbnail_url = obj.getString("cover") thumbnail_url = obj["cover"]!!.jsonPrimitive.content
author = obj.getJSONArray("authors").joinField(0) author = obj["authors"]!!.jsonArray.joinField(0)
artist = obj.getJSONArray("artists").joinField(0) artist = obj["artists"]!!.jsonArray.joinField(0)
genre = obj.getJSONArray("categories").joinField("name") genre = obj["categories"]!!.jsonArray.joinField("name")
status = if (obj.getBoolean("completed")) status = if (obj["completed"]!!.jsonPrimitive.boolean)
SManga.COMPLETED else SManga.ONGOING SManga.COMPLETED else SManga.ONGOING
} }
@ -78,18 +80,16 @@ val SChapter.path: String
* *
* @param obj The object containing the chapter info. * @param obj The object containing the chapter info.
*/ */
fun SChapter.fromJSON(obj: JSONObject) = apply { fun SChapter.fromJSON(obj: JsonObject) = apply {
url = obj.getString("url") url = obj["url"]!!.jsonPrimitive.content
chapter_number = obj.optString("chapter", "-1").toFloat() chapter_number = obj["chapter"]?.jsonPrimitive?.content?.toFloatOrNull() ?: -1f
date_upload = MangAdventure.httpDateToTimestamp(obj.getString("date")) date_upload = MangAdventure.httpDateToTimestamp(obj["date"]!!.jsonPrimitive.content)
scanlator = obj.getJSONArray("groups").joinField("name", " & ") scanlator = obj["groups"]!!.jsonArray.joinField("name", " & ")
name = obj.optString( name = obj["full_title"]?.jsonPrimitive?.contentOrNull
"full_title", ?: buildString {
buildString { obj["volume"]?.jsonPrimitive?.intOrNull?.let { if (it != 0) append("Vol. $it, ") }
obj.optInt("volume").let { if (it != 0) append("Vol. $it, ") }
append("Ch. ${chapter_number.format("#.#")}: ") append("Ch. ${chapter_number.format("#.#")}: ")
append(obj.getString("title")) append(obj["title"]!!.jsonPrimitive.content)
} }
) if (obj["final"]!!.jsonPrimitive.boolean) name += " [END]"
if (obj.getBoolean("final")) name += " [END]"
} }

View File

@ -9,7 +9,7 @@ class MangAdventureGenerator : ThemeSourceGenerator {
override val themeClass = "MangAdventure" override val themeClass = "MangAdventure"
override val baseVersionCode = 1 override val baseVersionCode = 2
override val sources = listOf( override val sources = listOf(
SingleLang("Arc-Relight", "https://arc-relight.com", "en", className = "ArcRelight"), SingleLang("Arc-Relight", "https://arc-relight.com", "en", className = "ArcRelight"),

View File

@ -11,6 +11,9 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Cookie import okhttp3.Cookie
import okhttp3.CookieJar import okhttp3.CookieJar
import okhttp3.Headers import okhttp3.Headers
@ -19,10 +22,10 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.json.JSONObject
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Locale import java.util.Locale
@ -76,6 +79,8 @@ open class Webtoons(
} }
} }
private val json: Json by injectLazy()
override fun popularMangaSelector() = "not using" override fun popularMangaSelector() = "not using"
override fun latestUpdatesSelector() = "div#dailyList > $day li > a" override fun latestUpdatesSelector() = "div#dailyList > $day li > a"
@ -262,14 +267,16 @@ open class Webtoons(
val docUrl = docUrlRegex.find(docString)!!.destructured.toList()[0] val docUrl = docUrlRegex.find(docString)!!.destructured.toList()[0]
val motiontoonPath = motiontoonPathRegex.find(docString)!!.destructured.toList()[0] val motiontoonPath = motiontoonPathRegex.find(docString)!!.destructured.toList()[0]
val motiontoonResponse = client.newCall(GET(docUrl, headers)).execute()
val motiontoonJson = JSONObject(client.newCall(GET(docUrl, headers)).execute().body!!.string()).getJSONObject("assets").getJSONObject("image") val motiontoonJson = json.parseToJsonElement(motiontoonResponse.body!!.string()).jsonObject
val motiontoonImages = motiontoonJson["assets"]!!.jsonObject["image"]!!.jsonObject
val keys = motiontoonJson.keys().asSequence().toList().filter { it.contains("layer") } return motiontoonImages.entries
.filter { it.key.contains("layer") }
return keys.mapIndexed { i, key -> .mapIndexed { i, entry ->
Page(i, "", motiontoonPath + motiontoonJson.getString(key)) Page(i, "", motiontoonPath + entry.value.jsonPrimitive.content)
} }
} }
companion object { companion object {

View File

@ -10,7 +10,7 @@ class WebtoonsGenerator : ThemeSourceGenerator {
override val themeClass = "Webtoons" override val themeClass = "Webtoons"
override val baseVersionCode: Int = 1 override val baseVersionCode: Int = 2
override val sources = listOf( override val sources = listOf(
MultiLang("Webtoons.com", "https://www.webtoons.com", listOf("en", "fr", "es", "id", "th", "zh"), className = "WebtoonsFactory", pkgName = "webtoons", overrideVersionCode = 29), MultiLang("Webtoons.com", "https://www.webtoons.com", listOf("en", "fr", "es", "id", "th", "zh"), className = "WebtoonsFactory", pkgName = "webtoons", overrideVersionCode = 29),

View File

@ -7,22 +7,32 @@ import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.int
import kotlinx.serialization.json.intOrNull
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.long
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.json.JSONObject
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
import java.util.ArrayList import uy.kohesive.injekt.injectLazy
open class WebtoonsTranslate ( open class WebtoonsTranslate(
override val name: String, override val name: String,
override val baseUrl: String, override val baseUrl: String,
override val lang: String, override val lang: String,
private val translateLangCode: String private val translateLangCode: String
) : Webtoons(name, baseUrl, lang) { ) : Webtoons(name, baseUrl, lang) {
// popularMangaRequest already returns manga sorted by latest update // popularMangaRequest already returns manga sorted by latest update
override val supportsLatest = false override val supportsLatest = false
@ -30,10 +40,9 @@ open class WebtoonsTranslate (
private val mobileBaseUrl = "https://m.webtoons.com".toHttpUrlOrNull()!! private val mobileBaseUrl = "https://m.webtoons.com".toHttpUrlOrNull()!!
private val thumbnailBaseUrl = "https://mwebtoon-phinf.pstatic.net" private val thumbnailBaseUrl = "https://mwebtoon-phinf.pstatic.net"
private val pageListUrlPattern = "/lineWebtoon/ctrans/translatedEpisodeDetail_jsonp.json?titleNo=%s&episodeNo=%d&languageCode=%s&teamVersion=%d"
private val pageSize = 24 private val pageSize = 24
private val json: Json by injectLazy()
override fun headersBuilder(): Headers.Builder = super.headersBuilder() override fun headersBuilder(): Headers.Builder = super.headersBuilder()
.removeAll("Referer") .removeAll("Referer")
@ -57,44 +66,38 @@ open class WebtoonsTranslate (
override fun popularMangaParse(response: Response): MangasPage { override fun popularMangaParse(response: Response): MangasPage {
val offset = response.request.url.queryParameter("offset")!!.toInt() val offset = response.request.url.queryParameter("offset")!!.toInt()
var totalCount: Int val result = json.parseToJsonElement(response.body!!.string()).jsonObject
val mangas = mutableListOf<SManga>() val responseCode = result["code"]!!.jsonPrimitive.content
JSONObject(response.body!!.string()).let { json -> if (responseCode != "000") {
json.getString("code").let { code -> throw Exception("Error getting popular manga: error code $responseCode")
if (code != "000") throw Exception("Error getting popular manga: error code $code")
}
json.getJSONObject("result").let { results ->
totalCount = results.getInt("totalCount")
results.getJSONArray("titleList").let { array ->
for (i in 0 until array.length()) {
mangas.add(mangaFromJson(array[i] as JSONObject))
}
}
}
} }
return MangasPage(mangas, totalCount > pageSize + offset) val titles = result["result"]!!.jsonObject
val totalCount = titles["totalCount"]!!.jsonPrimitive.int
val mangaList = titles["titleList"]!!.jsonArray
.map { mangaFromJson(it.jsonObject) }
return MangasPage(mangaList, hasNextPage = totalCount > pageSize + offset)
} }
private fun mangaFromJson(json: JSONObject): SManga { private fun mangaFromJson(manga: JsonObject): SManga {
val relativeThumnailURL = json.getString("thumbnailIPadUrl") val relativeThumnailURL = manga["thumbnailIPadUrl"]?.jsonPrimitive?.contentOrNull
?: json.getString("thumbnailMobileUrl") ?: manga["thumbnailMobileUrl"]?.jsonPrimitive?.contentOrNull
return SManga.create().apply { return SManga.create().apply {
title = json.getString("representTitle") title = manga["representTitle"]!!.jsonPrimitive.content
author = json.getString("writeAuthorName") author = manga["writeAuthorName"]!!.jsonPrimitive.content
artist = json.getString("pictureAuthorName") ?: author artist = manga["pictureAuthorName"]?.jsonPrimitive?.contentOrNull ?: author
thumbnail_url = if (relativeThumnailURL != null) "$thumbnailBaseUrl$relativeThumnailURL" else null thumbnail_url = if (relativeThumnailURL != null) "$thumbnailBaseUrl$relativeThumnailURL" else null
status = SManga.UNKNOWN status = SManga.UNKNOWN
url = mobileBaseUrl url = mobileBaseUrl
.resolve("/translate/episodeList")!! .resolve("/translate/episodeList")!!
.newBuilder() .newBuilder()
.addQueryParameter("titleNo", json.getInt("titleNo").toString()) .addQueryParameter("titleNo", manga["titleNo"]!!.jsonPrimitive.int.toString())
.addQueryParameter("languageCode", translateLangCode) .addQueryParameter("languageCode", translateLangCode)
.addQueryParameter("teamVersion", json.optInt("teamVersion", 0).toString()) .addQueryParameter("teamVersion", (manga["teamVersion"]?.jsonPrimitive?.intOrNull ?: 0).toString())
.build() .build()
.toString() .toString()
} }
@ -116,24 +119,18 @@ open class WebtoonsTranslate (
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = mangaRequest(page, 200) override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = mangaRequest(page, 200)
private fun searchMangaParse(response: Response, query: String): MangasPage { private fun searchMangaParse(response: Response, query: String): MangasPage {
val mangas = mutableListOf<SManga>() val result = json.parseToJsonElement(response.body!!.string()).jsonObject
val responseCode = result["code"]!!.jsonPrimitive.content
JSONObject(response.body!!.string()).let { json -> if (responseCode != "000") {
json.getString("code").let { code -> throw Exception("Error getting manga: error code $responseCode")
if (code != "000") throw Exception("Error getting manga: error code $code")
}
json.getJSONObject("result").getJSONArray("titleList").let { array ->
for (i in 0 until array.length()) {
(array[i] as JSONObject).let { jsonManga ->
if (jsonManga.getString("representTitle").contains(query, ignoreCase = true))
mangas.add(mangaFromJson(jsonManga))
}
}
}
} }
return MangasPage(mangas, false) val mangaList = result["result"]!!.jsonObject["titleList"]!!.jsonArray
.map { mangaFromJson(it.jsonObject) }
.filter { it.title.contains(query, ignoreCase = true) }
return MangasPage(mangaList, false)
} }
override fun mangaDetailsRequest(manga: SManga): Request { override fun mangaDetailsRequest(manga: SManga): Request {
@ -170,7 +167,7 @@ open class WebtoonsTranslate (
override fun chapterListRequest(manga: SManga): Request { override fun chapterListRequest(manga: SManga): Request {
val titleNo = manga.url.toHttpUrlOrNull()!! val titleNo = manga.url.toHttpUrlOrNull()!!
.queryParameter("titleNo") .queryParameter("titleNo")
val chapterUrl = apiBaseUrl val chapterListUrl = apiBaseUrl
.resolve("/lineWebtoon/ctrans/translatedEpisodes_jsonp.json")!! .resolve("/lineWebtoon/ctrans/translatedEpisodes_jsonp.json")!!
.newBuilder() .newBuilder()
.addQueryParameter("titleNo", titleNo) .addQueryParameter("titleNo", titleNo)
@ -178,38 +175,40 @@ open class WebtoonsTranslate (
.addQueryParameter("offset", "0") .addQueryParameter("offset", "0")
.addQueryParameter("limit", "10000") .addQueryParameter("limit", "10000")
.toString() .toString()
return GET(chapterUrl, mobileHeaders) return GET(chapterListUrl, mobileHeaders)
} }
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val chapterData = response.body!!.string() val result = json.parseToJsonElement(response.body!!.string()).jsonObject
val chapterJson = JSONObject(chapterData) val responseCode = result["code"]!!.jsonPrimitive.content
val responseCode = chapterJson.getString("code")
if (responseCode != "000") { if (responseCode != "000") {
val message = chapterJson.optString("message", "error code $responseCode") val message = result["message"]?.jsonPrimitive?.content ?: "error code $responseCode"
throw Exception("Error getting chapter list: $message") throw Exception("Error getting chapter list: $message")
} }
val results = chapterJson.getJSONObject("result").getJSONArray("episodes")
val ret = ArrayList<SChapter>() return result["result"]!!.jsonObject["episodes"]!!.jsonArray
for (i in 0 until results.length()) { .filter { it.jsonObject["translateCompleted"]!!.jsonPrimitive.boolean }
val result = results.getJSONObject(i) .map { parseChapterJson(it.jsonObject) }
if (result.getBoolean("translateCompleted")) { .reversed()
ret.add(parseChapterJson(result))
}
}
ret.reverse()
return ret
} }
private fun parseChapterJson(obj: JSONObject) = SChapter.create().apply { private fun parseChapterJson(obj: JsonObject): SChapter = SChapter.create().apply {
name = obj.getString("title") + " #" + obj.getString("episodeSeq") name = obj["title"]!!.jsonPrimitive.content + " #" + obj["episodeSeq"]!!.jsonPrimitive.int
chapter_number = obj.getInt("episodeSeq").toFloat() chapter_number = obj["episodeSeq"]!!.jsonPrimitive.int.toFloat()
date_upload = obj.getLong("updateYmdt") date_upload = obj["updateYmdt"]!!.jsonPrimitive.long
scanlator = obj.getString("teamVersion") scanlator = obj["teamVersion"]!!.jsonPrimitive.int.takeIf { it != 0 }?.toString() ?: "(wiki)"
if (scanlator == "0") {
scanlator = "(wiki)" val chapterUrl = apiBaseUrl
} .resolve("/lineWebtoon/ctrans/translatedEpisodeDetail_jsonp.json")!!
url = String.format(pageListUrlPattern, obj.getInt("titleNo"), obj.getInt("episodeNo"), obj.getString("languageCode"), obj.getInt("teamVersion")) .newBuilder()
.addQueryParameter("titleNo", obj["titleNo"]!!.jsonPrimitive.int.toString())
.addQueryParameter("episodeNo", obj["episodeNo"]!!.jsonPrimitive.int.toString())
.addQueryParameter("languageCode", obj["languageCode"]!!.jsonPrimitive.content)
.addQueryParameter("teamVersion", obj["teamVersion"]!!.jsonPrimitive.int.toString())
.toString()
setUrlWithoutDomain(chapterUrl)
} }
override fun pageListRequest(chapter: SChapter): Request { override fun pageListRequest(chapter: SChapter): Request {
@ -217,14 +216,12 @@ open class WebtoonsTranslate (
} }
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val pageJson = JSONObject(response.body!!.string()) val result = json.parseToJsonElement(response.body!!.string()).jsonObject
val results = pageJson.getJSONObject("result").getJSONArray("imageInfo")
val ret = ArrayList<Page>() return result["result"]!!.jsonObject["imageInfo"]!!.jsonArray
for (i in 0 until results.length()) { .mapIndexed { i, jsonEl ->
val result = results.getJSONObject(i) Page(i, "", jsonEl.jsonObject["imageUrl"]!!.jsonPrimitive.content)
ret.add(Page(i, "", result.getString("imageUrl"))) }
}
return ret
} }
override fun getFilterList(): FilterList = FilterList() override fun getFilterList(): FilterList = FilterList()

View File

@ -9,7 +9,7 @@ class WebtoonsTranslateGenerator : ThemeSourceGenerator {
override val themeClass = "WebtoonsTranslation" override val themeClass = "WebtoonsTranslation"
override val baseVersionCode: Int = 1 override val baseVersionCode: Int = 2
override val sources = listOf( override val sources = listOf(
MultiLang("Webtoons.com Translations", "https://translate.webtoons.com", listOf("en", "zh-hans", "zh-hant", "th", "id", "fr", "vi", "ru", "ar", "fil", "de", "hi", "it", "ja", "pt-BR", "tr", "ms", "pl", "pt", "bg", "da", "nl", "ro", "mn", "el", "lt", "cs", "sv", "bn", "fa", "uk", "es"), className = "WebtoonsTranslateFactory", pkgName = "webtoonstranslate", overrideVersionCode = 1), MultiLang("Webtoons.com Translations", "https://translate.webtoons.com", listOf("en", "zh-hans", "zh-hant", "th", "id", "fr", "vi", "ru", "ar", "fil", "de", "hi", "it", "ja", "pt-BR", "tr", "ms", "pl", "pt", "bg", "da", "nl", "ro", "mn", "el", "lt", "cs", "sv", "bn", "fa", "uk", "es"), className = "WebtoonsTranslateFactory", pkgName = "webtoonstranslate", overrideVersionCode = 1),

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.multisrc.wpmangareader package eu.kanade.tachiyomi.multisrc.wpmangareader
import android.util.Log
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
@ -11,16 +10,19 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.json.JSONArray
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
import rx.Single import rx.Single
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@ -36,6 +38,8 @@ abstract class WPMangaReader(
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
private val json: Json by injectLazy()
// popular // popular
override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", FilterList(OrderByFilter(5))) override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", FilterList(OrderByFilter(5)))
override fun popularMangaParse(response: Response) = searchMangaParse(response) override fun popularMangaParse(response: Response) = searchMangaParse(response)
@ -65,7 +69,7 @@ abstract class WPMangaReader(
* @returns An identifier for a manga, or null if none could be found * @returns An identifier for a manga, or null if none could be found
*/ */
protected open fun mangaIdFromUrl(s: String): Single<String?> { protected open fun mangaIdFromUrl(s: String): Single<String?> {
var baseMangaUrl = "$baseUrl$mangaUrlDirectory".toHttpUrlOrNull()!! val baseMangaUrl = "$baseUrl$mangaUrlDirectory".toHttpUrlOrNull()!!
return s.toHttpUrlOrNull()?.let { url -> return s.toHttpUrlOrNull()?.let { url ->
fun pathLengthIs(url: HttpUrl, n: Int, strict: Boolean = false) = url.pathSegments.size == n && url.pathSegments[n - 1].isNotEmpty() || (!strict && url.pathSegments.size == n + 1 && url.pathSegments[n].isEmpty()) fun pathLengthIs(url: HttpUrl, n: Int, strict: Boolean = false) = url.pathSegments.size == n && url.pathSegments[n - 1].isNotEmpty() || (!strict && url.pathSegments.size == n + 1 && url.pathSegments[n].isEmpty())
val isMangaUrl = listOf( val isMangaUrl = listOf(
@ -226,7 +230,7 @@ abstract class WPMangaReader(
open val pageSelector = "div#readerarea img" open val pageSelector = "div#readerarea img"
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
var pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
document.select(pageSelector) document.select(pageSelector)
.filterNot { it.attr("src").isNullOrEmpty() } .filterNot { it.attr("src").isNullOrEmpty() }
.mapIndexed { i, img -> pages.add(Page(i, "", img.attr("abs:src"))) } .mapIndexed { i, img -> pages.add(Page(i, "", img.attr("abs:src"))) }
@ -236,11 +240,12 @@ abstract class WPMangaReader(
val docString = document.toString() val docString = document.toString()
val imageListRegex = Regex("\\\"images.*?:.*?(\\[.*?\\])") val imageListRegex = Regex("\\\"images.*?:.*?(\\[.*?\\])")
val imageListJson = imageListRegex.find(docString)!!.destructured.toList()[0]
val imageList = JSONArray(imageListRegex.find(docString)!!.destructured.toList()[0]) val imageList = json.parseToJsonElement(imageListJson).jsonArray
for (i in 0 until imageList.length()) { pages += imageList.mapIndexed { i, jsonEl ->
pages.add(Page(i, "", imageList.getString(i))) Page(i, "", jsonEl.jsonPrimitive.content)
} }
return pages return pages

View File

@ -9,7 +9,7 @@ class WPMangaReaderGenerator : ThemeSourceGenerator {
override val themeClass = "WPMangaReader" override val themeClass = "WPMangaReader"
override val baseVersionCode: Int = 6 override val baseVersionCode: Int = 7
override val sources = listOf( override val sources = listOf(
SingleLang("Kiryuu", "https://kiryuu.id", "id"), SingleLang("Kiryuu", "https://kiryuu.id", "id"),

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.multisrc.wpmangastream package eu.kanade.tachiyomi.multisrc.wpmangastream
//import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor // added to override
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
@ -12,17 +11,20 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.json.JSONArray
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.jsoup.select.Elements import org.jsoup.select.Elements
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Locale import java.util.Locale
@ -67,17 +69,16 @@ abstract class WPMangaStream(
private fun getShowThumbnail(): Int = preferences.getInt(SHOW_THUMBNAIL_PREF, 0) private fun getShowThumbnail(): Int = preferences.getInt(SHOW_THUMBNAIL_PREF, 0)
//private val rateLimitInterceptor = RateLimitInterceptor(4)
override val client: OkHttpClient = network.cloudflareClient.newBuilder() override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS) .connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS)
//.addNetworkInterceptor(rateLimitInterceptor)
.build() .build()
protected fun Element.imgAttr(): String = if (this.hasAttr("data-src")) this.attr("abs:data-src") else this.attr("abs:src") protected fun Element.imgAttr(): String = if (this.hasAttr("data-src")) this.attr("abs:data-src") else this.attr("abs:src")
protected fun Elements.imgAttr(): String = this.first().imgAttr() protected fun Elements.imgAttr(): String = this.first().imgAttr()
private val json: Json by injectLazy()
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/manga/?page=$page&order=popular", headers) return GET("$baseUrl/manga/?page=$page&order=popular", headers)
} }
@ -167,7 +168,7 @@ abstract class WPMangaStream(
// add alternative name to manga description // add alternative name to manga description
document.select(altNameSelector).firstOrNull()?.ownText()?.let { document.select(altNameSelector).firstOrNull()?.ownText()?.let {
if (it.isEmpty().not() && it !="N/A" && it != "-") { if (it.isEmpty().not() && it != "N/A" && it != "-") {
description += when { description += when {
description!!.isEmpty() -> altName + it description!!.isEmpty() -> altName + it
else -> "\n\n$altName" + it else -> "\n\n$altName" + it
@ -182,7 +183,7 @@ abstract class WPMangaStream(
open val altNameSelector = ".alternative, .wd-full:contains(Alt) span, .alter, .seriestualt" open val altNameSelector = ".alternative, .wd-full:contains(Alt) span, .alter, .seriestualt"
open val altName = "Alternative Name" + ": " open val altName = "Alternative Name" + ": "
protected fun parseStatus(element: String?): Int = when { protected open fun parseStatus(element: String?): Int = when {
element == null -> SManga.UNKNOWN element == null -> SManga.UNKNOWN
listOf("ongoing", "publishing").any { it.contains(element, ignoreCase = true) } -> SManga.ONGOING listOf("ongoing", "publishing").any { it.contains(element, ignoreCase = true) } -> SManga.ONGOING
listOf("completed").any { it.contains(element, ignoreCase = true) } -> SManga.COMPLETED listOf("completed").any { it.contains(element, ignoreCase = true) } -> SManga.COMPLETED
@ -265,24 +266,26 @@ abstract class WPMangaStream(
open val pageSelector = "div#readerarea img" open val pageSelector = "div#readerarea img"
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
var pages = mutableListOf<Page>() val htmlPages = document.select(pageSelector)
document.select(pageSelector)
.filterNot { it.attr("src").isNullOrEmpty() } .filterNot { it.attr("src").isNullOrEmpty() }
.mapIndexed { i, img -> pages.add(Page(i, "", img.attr("abs:src"))) } .mapIndexed { i, img -> Page(i, "", img.attr("abs:src")) }
.toMutableList()
// Some wpmangastream sites now load pages via javascript
if (pages.isNotEmpty()) { return pages }
val docString = document.toString() val docString = document.toString()
val imageListRegex = Regex("\\\"images.*?:.*?(\\[.*?\\])") val imageListRegex = Regex("\\\"images.*?:.*?(\\[.*?\\])")
val imageListJson = imageListRegex.find(docString)!!.destructured.toList()[0]
val imageList = JSONArray(imageListRegex.find(docString)!!.destructured.toList()[0]) val imageList = json.parseToJsonElement(imageListJson).jsonArray
for (i in 0 until imageList.length()) { val scriptPages = imageList.mapIndexed { i, jsonEl ->
pages.add(Page(i, "", imageList.getString(i))) Page(i, "", jsonEl.jsonPrimitive.content)
} }
return pages if (htmlPages.size < scriptPages.size) {
htmlPages += scriptPages
}
return htmlPages
} }
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")

View File

@ -9,10 +9,10 @@ class WPMangaStreamGenerator : ThemeSourceGenerator {
override val themeClass = "WPMangaStream" override val themeClass = "WPMangaStream"
override val baseVersionCode: Int = 4 override val baseVersionCode: Int = 5
override val sources = listOf( override val sources = listOf(
SingleLang("Asura Scans", "override url", "en", overrideVersionCode = 1), SingleLang("Asura Scans", "https://www.asurascans.com", "en", overrideVersionCode = 1),
SingleLang("KlanKomik", "https://klankomik.com", "id"), SingleLang("KlanKomik", "https://klankomik.com", "id"),
SingleLang("MasterKomik", "https://masterkomik.com", "id"), SingleLang("MasterKomik", "https://masterkomik.com", "id"),
SingleLang("Kaisar Komik", "https://kaisarkomik.com", "id"), SingleLang("Kaisar Komik", "https://kaisarkomik.com", "id"),
@ -34,7 +34,7 @@ class WPMangaStreamGenerator : ThemeSourceGenerator {
SingleLang("MangaSwat", "https://mangaswat.com", "ar"), SingleLang("MangaSwat", "https://mangaswat.com", "ar"),
SingleLang("Manga Raw.org", "https://mangaraw.org", "ja", className = "MangaRawOrg", overrideVersionCode = 1), SingleLang("Manga Raw.org", "https://mangaraw.org", "ja", className = "MangaRawOrg", overrideVersionCode = 1),
SingleLang("Manga Pro Z", "https://mangaproz.com", "ar"), SingleLang("Manga Pro Z", "https://mangaproz.com", "ar"),
SingleLang("Silence Scan", "https://silencescan.net", "pt-BR", overrideVersionCode = 1), SingleLang("Silence Scan", "https://silencescan.net", "pt-BR", overrideVersionCode = 2),
SingleLang("Kuma Scans (Kuma Translation)", "https://kumascans.com", "en", className = "KumaScans"), SingleLang("Kuma Scans (Kuma Translation)", "https://kumascans.com", "en", className = "KumaScans"),
SingleLang("Tempest Manga", "https://manga.tempestfansub.com", "tr"), SingleLang("Tempest Manga", "https://manga.tempestfansub.com", "tr"),
SingleLang("xCaliBR Scans", "https://xcalibrscans.com", "en", overrideVersionCode = 2), SingleLang("xCaliBR Scans", "https://xcalibrscans.com", "en", overrideVersionCode = 2),

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'Cubari' extName = 'Cubari'
pkgNameSuffix = "all.cubari" pkgNameSuffix = "all.cubari"
extClass = '.CubariFactory' extClass = '.CubariFactory'
extVersionCode = 5 extVersionCode = 6
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -11,21 +11,32 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.long
import okhttp3.Headers import okhttp3.Headers
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.json.JSONArray
import org.json.JSONObject
import rx.Observable import rx.Observable
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
open class Cubari(override val lang: String) : HttpSource() { open class Cubari(override val lang: String) : HttpSource() {
final override val name = "Cubari" final override val name = "Cubari"
final override val baseUrl = "https://cubari.moe" final override val baseUrl = "https://cubari.moe"
final override val supportsLatest = true final override val supportsLatest = true
private val json: Json by injectLazy()
override fun headersBuilder() = Headers.Builder().apply { override fun headersBuilder() = Headers.Builder().apply {
add( add(
"User-Agent", "User-Agent",
@ -50,7 +61,8 @@ open class Cubari(override val lang: String) : HttpSource() {
} }
override fun latestUpdatesParse(response: Response): MangasPage { override fun latestUpdatesParse(response: Response): MangasPage {
return parseMangaList(JSONArray(response.body!!.string()), SortType.UNPINNED) val result = json.parseToJsonElement(response.body!!.string()).jsonArray
return parseMangaList(result, SortType.UNPINNED)
} }
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
@ -67,7 +79,8 @@ open class Cubari(override val lang: String) : HttpSource() {
} }
override fun popularMangaParse(response: Response): MangasPage { override fun popularMangaParse(response: Response): MangasPage {
return parseMangaList(JSONArray(response.body!!.string()), SortType.PINNED) val result = json.parseToJsonElement(response.body!!.string()).jsonArray
return parseMangaList(result, SortType.PINNED)
} }
override fun fetchMangaDetails(manga: SManga): Observable<SManga> { override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
@ -86,7 +99,8 @@ open class Cubari(override val lang: String) : HttpSource() {
} }
private fun mangaDetailsParse(response: Response, manga: SManga): SManga { private fun mangaDetailsParse(response: Response, manga: SManga): SManga {
return parseMangaFromApi(JSONObject(response.body!!.string()), manga) val result = json.parseToJsonElement(response.body!!.string()).jsonObject
return parseManga(result, manga)
} }
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
@ -139,7 +153,7 @@ open class Cubari(override val lang: String) : HttpSource() {
GET("$baseUrl${chapter.url}", headers) GET("$baseUrl${chapter.url}", headers)
} }
else -> { else -> {
var url = chapter.url.split("/") val url = chapter.url.split("/")
val source = url[2] val source = url[2]
val slug = url[3] val slug = url[3]
@ -150,55 +164,49 @@ open class Cubari(override val lang: String) : HttpSource() {
private fun directPageListParse(response: Response): List<Page> { private fun directPageListParse(response: Response): List<Page> {
val res = response.body!!.string() val res = response.body!!.string()
val pages = JSONArray(res) val pages = json.parseToJsonElement(res).jsonArray
val pageArray = ArrayList<Page>()
for (i in 0 until pages.length()) { return pages.mapIndexed { i, jsonEl ->
val page = if (pages.optJSONObject(i) != null) { val page = if (jsonEl is JsonObject) {
pages.getJSONObject(i).getString("src") jsonEl.jsonObject["src"]!!.jsonPrimitive.content
} else { } else {
pages[i] jsonEl.jsonPrimitive.content
} }
pageArray.add(Page(i + 1, "", page.toString()))
Page(i, "", page)
} }
return pageArray
} }
private fun seriesJsonPageListParse(response: Response, chapter: SChapter): List<Page> { private fun seriesJsonPageListParse(response: Response, chapter: SChapter): List<Page> {
val res = response.body!!.string() val jsonObj = json.parseToJsonElement(response.body!!.string()).jsonObject
val json = JSONObject(res) val groups = jsonObj["groups"]!!.jsonObject
val groups = json.getJSONObject("groups") val groupMap = groups.entries
val groupIter = groups.keys() .map { Pair(it.value.jsonPrimitive.content, it.key) }
val groupMap = HashMap<String, String>() .toMap()
while (groupIter.hasNext()) { val chapters = jsonObj["chapters"]!!.jsonObject
val groupKey = groupIter.next()
groupMap[groups.getString(groupKey)] = groupKey
}
val chapters = json.getJSONObject("chapters") val pages = if (chapters[chapter.chapter_number.toString()] != null) {
chapters[chapter.chapter_number.toString()]!!
val pages = if (chapters.has(chapter.chapter_number.toString())) { .jsonObject["groups"]!!
chapters .jsonObject[groupMap[chapter.scanlator]]!!
.getJSONObject(chapter.chapter_number.toString()) .jsonArray
.getJSONObject("groups")
.getJSONArray(groupMap[chapter.scanlator])
} else { } else {
chapters chapters[chapter.chapter_number.toInt().toString()]!!
.getJSONObject(chapter.chapter_number.toInt().toString()) .jsonObject["groups"]!!
.getJSONObject("groups") .jsonObject[groupMap[chapter.scanlator]]!!
.getJSONArray(groupMap[chapter.scanlator]) .jsonArray
} }
val pageArray = ArrayList<Page>()
for (i in 0 until pages.length()) { return pages.mapIndexed { i, jsonEl ->
val page = if (pages.optJSONObject(i) != null) { val page = if (jsonEl is JsonObject) {
pages.getJSONObject(i).getString("src") jsonEl.jsonObject["src"]!!.jsonPrimitive.content
} else { } else {
pages[i] jsonEl.jsonPrimitive.content
} }
pageArray.add(Page(i + 1, "", page.toString()))
Page(i, "", page)
} }
return pageArray
} }
// Stub // Stub
@ -241,125 +249,105 @@ open class Cubari(override val lang: String) : HttpSource() {
} }
private fun searchMangaParse(response: Response, query: String): MangasPage { private fun searchMangaParse(response: Response, query: String): MangasPage {
return parseSearchList(JSONObject(response.body!!.string()), query) val result = json.parseToJsonElement(response.body!!.string()).jsonObject
return parseSearchList(result, query)
} }
// ------------- Helpers and whatnot --------------- // ------------- Helpers and whatnot ---------------
private fun parseChapterList(payload: String, manga: SManga): List<SChapter> { private fun parseChapterList(payload: String, manga: SManga): List<SChapter> {
val json = JSONObject(payload) val jsonObj = json.parseToJsonElement(payload).jsonObject
val groups = json.getJSONObject("groups") val groups = jsonObj["groups"]!!.jsonObject
val chapters = json.getJSONObject("chapters") val chapters = jsonObj["chapters"]!!.jsonObject
val seriesSlug = json.getString("slug") val seriesSlug = jsonObj["slug"]!!.jsonPrimitive.content
val chapterList = ArrayList<SChapter>()
val iter = chapters.keys()
val seriesPrefs = Injekt.get<Application>().getSharedPreferences("source_${id}_updateTime:$seriesSlug", 0) val seriesPrefs = Injekt.get<Application>().getSharedPreferences("source_${id}_updateTime:$seriesSlug", 0)
val seriesPrefsEditor = seriesPrefs.edit() val seriesPrefsEditor = seriesPrefs.edit()
while (iter.hasNext()) { val chapterList = chapters.entries.flatMap { chapterEntry ->
val chapterNum = iter.next() val chapterNum = chapterEntry.key
val chapterObj = chapters.getJSONObject(chapterNum) val chapterObj = chapterEntry.value.jsonObject
val chapterGroups = chapterObj.getJSONObject("groups") val chapterGroups = chapterObj["groups"]!!.jsonObject
val groupsIter = chapterGroups.keys()
while (groupsIter.hasNext()) { chapterGroups.entries.map { groupEntry ->
val groupNum = groupsIter.next() val groupNum = groupEntry.key
val chapter = SChapter.create()
chapter.scanlator = groups.getString(groupNum) SChapter.create().apply {
scanlator = groups[groupNum]!!.jsonPrimitive.content
//Api for gist (and some others maybe) doesn't give a "release_date" so we will use the Manga update time. chapter_number = chapterNum.toFloatOrNull() ?: -1f
//So when new chapter comes the manga will go on top if sortinf is set to "Last Updated"
//Code by ivaniskandar (Implemented on CatManga extension.) date_upload = if (chapterObj["release_date"] != null) {
chapterObj["release_date"]!!.jsonObject[groupNum]!!.jsonPrimitive.long * 1000
if (chapterObj.has("release_date")) { } else {
chapter.date_upload = val currentTimeMillis = System.currentTimeMillis()
chapterObj.getJSONObject("release_date").getLong(groupNum) * 1000
} else { if (!seriesPrefs.contains(chapterNum)) {
val currentTimeMillis = System.currentTimeMillis() seriesPrefsEditor.putLong(chapterNum, currentTimeMillis)
if (!seriesPrefs.contains(chapterNum)) { }
seriesPrefsEditor.putLong(chapterNum, currentTimeMillis)
seriesPrefs.getLong(chapterNum, currentTimeMillis)
} }
chapter.date_upload = seriesPrefs.getLong(chapterNum, currentTimeMillis)
} name = if (chapterObj["volume"]!!.jsonPrimitive.content != "Uncategorized") {
chapter.name = if (chapterObj.getString("volume") != "Uncategorized") { // Output "Vol. 1 Ch. 1 - Chapter Name"
"Vol. " + chapterObj["volume"]!!.jsonPrimitive.content + " Ch. " +
"Vol. " + chapterObj.getString("volume") + " Ch. " + chapterNum + " - " + chapterObj.getString("title") chapterNum + " - " + chapterObj["title"]!!.jsonPrimitive.content
//Output "Vol. 1 Ch. 1 - Chapter Name" } else {
// Output "Ch. 1 - Chapter Name"
} else { "Ch. " + chapterNum + " - " + chapterObj["title"]!!.jsonPrimitive.content
}
"Ch. " + chapterNum + " - " + chapterObj.getString("title")
//Output "Ch. 1 - Chapter Name" url = if (chapterGroups[groupNum] != null) {
}
chapter.chapter_number = chapterNum.toFloat()
chapter.url =
if (chapterGroups.optJSONArray(groupNum) != null) {
"${manga.url}/$chapterNum/$groupNum" "${manga.url}/$chapterNum/$groupNum"
} else { } else {
chapterGroups.getString(groupNum) chapterGroups[groupNum]!!.jsonPrimitive.content
} }
chapterList.add(chapter) }
} }
} }
seriesPrefsEditor.apply() seriesPrefsEditor.apply()
return chapterList.reversed()
return chapterList.sortedByDescending { it.chapter_number }
} }
private fun parseMangaList(payload: JSONArray, sortType: SortType): MangasPage { private fun parseMangaList(payload: JsonArray, sortType: SortType): MangasPage {
val mangas = ArrayList<SManga>() val mangaList = payload.mapNotNull { jsonEl ->
val jsonObj = jsonEl.jsonObject
for (i in 0 until payload.length()) { val pinned = jsonObj["pinned"]!!.jsonPrimitive.boolean
val json = payload.getJSONObject(i)
val pinned = json.getBoolean("pinned")
if (sortType == SortType.PINNED && pinned) { if (sortType == SortType.PINNED && pinned) {
mangas.add(parseMangaFromRemoteStorage(json)) parseManga(jsonObj)
} else if (sortType == SortType.UNPINNED && !pinned) { } else if (sortType == SortType.UNPINNED && !pinned) {
mangas.add(parseMangaFromRemoteStorage(json)) parseManga(jsonObj)
} else {
null
} }
} }
return MangasPage(mangas, false) return MangasPage(mangaList, false)
} }
private fun parseSearchList(payload: JSONObject, query: String): MangasPage { private fun parseSearchList(payload: JsonObject, query: String): MangasPage {
val mangas = ArrayList<SManga>() val tempManga = SManga.create().apply {
val tempManga = SManga.create() url = "/read/$query"
tempManga.url = "/read/$query" }
mangas.add(parseMangaFromApi(payload, tempManga))
return MangasPage(mangas, false) val mangaList = listOf(parseManga(payload, tempManga))
return MangasPage(mangaList, false)
} }
private fun parseMangaFromRemoteStorage(json: JSONObject): SManga { private fun parseManga(jsonObj: JsonObject, mangaReference: SManga? = null): SManga =
val manga = SManga.create() SManga.create().apply {
manga.title = json.getString("title") title = jsonObj["title"]!!.jsonPrimitive.content
manga.artist = json.optString("artist", ARTIST_FALLBACK) artist = jsonObj["artist"]?.jsonPrimitive?.content ?: ARTIST_FALLBACK
manga.author = json.optString("author", AUTHOR_FALLBACK) author = jsonObj["author"]?.jsonPrimitive?.content ?: AUTHOR_FALLBACK
manga.description = json.optString("description", DESCRIPTION_FALLBACK) description = jsonObj["description"]?.jsonPrimitive?.content ?: DESCRIPTION_FALLBACK
manga.url = json.getString("url") url = mangaReference?.url ?: jsonObj["url"]!!.jsonPrimitive.content
manga.thumbnail_url = json.getString("coverUrl") thumbnail_url = jsonObj["coverUrl"]?.jsonPrimitive?.content
?: jsonObj["cover"]?.jsonPrimitive?.content ?: ""
return manga }
}
private fun parseMangaFromApi(json: JSONObject, mangaReference: SManga): SManga {
val manga = SManga.create()
manga.title = json.getString("title")
manga.artist = json.optString("artist", ARTIST_FALLBACK)
manga.author = json.optString("author", AUTHOR_FALLBACK)
manga.description = json.optString("description", DESCRIPTION_FALLBACK)
manga.url = mangaReference.url
manga.thumbnail_url = json.optString("cover", "")
return manga
}
// ----------------- Things we aren't supporting ----------------- // ----------------- Things we aren't supporting -----------------

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'Bilibili Comics' extName = 'Bilibili Comics'
pkgNameSuffix = 'en.bilibilicomics' pkgNameSuffix = 'en.bilibilicomics'
extClass = '.BilibiliComics' extClass = '.BilibiliComics'
extVersionCode = 2 extVersionCode = 3
libVersion = '1.2' libVersion = '1.2'
containsNsfw = true containsNsfw = true
} }

View File

@ -1,16 +1,5 @@
package eu.kanade.tachiyomi.extension.en.bilibilicomics package eu.kanade.tachiyomi.extension.en.bilibilicomics
import com.github.salomonbrys.kotson.array
import com.github.salomonbrys.kotson.bool
import com.github.salomonbrys.kotson.float
import com.github.salomonbrys.kotson.get
import com.github.salomonbrys.kotson.int
import com.github.salomonbrys.kotson.jsonArray
import com.github.salomonbrys.kotson.jsonObject
import com.github.salomonbrys.kotson.obj
import com.github.salomonbrys.kotson.string
import com.google.gson.JsonElement
import com.google.gson.JsonParser
import eu.kanade.tachiyomi.annotations.Nsfw import eu.kanade.tachiyomi.annotations.Nsfw
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
@ -21,6 +10,12 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.add
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
@ -30,6 +25,7 @@ import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response import okhttp3.Response
import org.jsoup.Jsoup import org.jsoup.Jsoup
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@ -55,14 +51,16 @@ class BilibiliComics : HttpSource() {
.add("Origin", baseUrl) .add("Origin", baseUrl)
.add("Referer", "$baseUrl/") .add("Referer", "$baseUrl/")
private val json: Json by injectLazy()
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
val requestPayload = jsonObject( val requestPayload = buildJsonObject {
"id" to FEATURED_ID, put("id", FEATURED_ID)
"isAll" to 0, put("isAll", 0)
"page_num" to 1, put("page_num", 1)
"page_size" to 6 put("page_size", 6)
) }
val requestBody = requestPayload.toString().toRequestBody(JSON_CONTENT_TYPE) val requestBody = requestPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
val newHeaders = headersBuilder() val newHeaders = headersBuilder()
.add("Content-Length", requestBody.contentLength().toString()) .add("Content-Length", requestBody.contentLength().toString())
@ -77,36 +75,36 @@ class BilibiliComics : HttpSource() {
} }
override fun popularMangaParse(response: Response): MangasPage { override fun popularMangaParse(response: Response): MangasPage {
val jsonResponse = response.asJson().obj val result = json.decodeFromString<BilibiliResultDto<BilibiliFeaturedDto>>(response.body!!.string())
if (jsonResponse["code"].int != 0) { if (result.code != 0) {
return MangasPage(emptyList(), hasNextPage = false) return MangasPage(emptyList(), hasNextPage = false)
} }
val comicList = jsonResponse["data"]["roll_six_comics"].array val comicList = result.data!!.rollSixComics
.map(::popularMangaFromObject) .map(::popularMangaFromObject)
return MangasPage(comicList, hasNextPage = false) return MangasPage(comicList, hasNextPage = false)
} }
private fun popularMangaFromObject(obj: JsonElement): SManga = SManga.create().apply { private fun popularMangaFromObject(comic: BilibiliComicDto): SManga = SManga.create().apply {
title = obj["title"].string title = comic.title
thumbnail_url = obj["vertical_cover"].string thumbnail_url = comic.verticalCover
url = "/detail/mc" + obj["comic_id"].int url = "/detail/mc${comic.comicId}"
} }
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val jsonPayload = jsonObject( val jsonPayload = buildJsonObject {
"area_id" to -1, put("area_id", -1)
"is_finish" to -1, put("is_finish", -1)
"is_free" to 1, put("is_free", 1)
"key_word" to query, put("key_word", query)
"order" to 0, put("order", 0)
"page_num" to page, put("page_num", page)
"page_size" to 9, put("page_size", 9)
"style_id" to -1 put("style_id", -1)
) }
val requestBody = jsonPayload.toString().toRequestBody(JSON_CONTENT_TYPE) val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
val refererUrl = "$baseUrl/search".toHttpUrl().newBuilder() val refererUrl = "$baseUrl/search".toHttpUrl().newBuilder()
.addQueryParameter("keyword", query) .addQueryParameter("keyword", query)
@ -126,22 +124,22 @@ class BilibiliComics : HttpSource() {
} }
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
val jsonResponse = response.asJson().obj val result = json.decodeFromString<BilibiliResultDto<BilibiliSearchDto>>(response.body!!.string())
if (jsonResponse["code"].int != 0) { if (result.code != 0) {
return MangasPage(emptyList(), hasNextPage = false) return MangasPage(emptyList(), hasNextPage = false)
} }
val comicList = jsonResponse["data"]["list"].array val comicList = result.data!!.list
.map(::searchMangaFromObject) .map(::searchMangaFromObject)
return MangasPage(comicList, hasNextPage = false) return MangasPage(comicList, hasNextPage = false)
} }
private fun searchMangaFromObject(obj: JsonElement): SManga = SManga.create().apply { private fun searchMangaFromObject(comic: BilibiliComicDto): SManga = SManga.create().apply {
title = Jsoup.parse(obj["title"].string).text() title = Jsoup.parse(comic.title).text()
thumbnail_url = obj["vertical_cover"].string thumbnail_url = comic.verticalCover
url = "/detail/mc" + obj["id"].int url = "/detail/mc${comic.id}"
} }
// Workaround to allow "Open in browser" use the real URL. // Workaround to allow "Open in browser" use the real URL.
@ -156,8 +154,8 @@ class BilibiliComics : HttpSource() {
private fun mangaDetailsApiRequest(manga: SManga): Request { private fun mangaDetailsApiRequest(manga: SManga): Request {
val comicId = manga.url.substringAfterLast("/mc").toInt() val comicId = manga.url.substringAfterLast("/mc").toInt()
val jsonPayload = jsonObject("comic_id" to comicId) val jsonPayload = buildJsonObject { put("comic_id", comicId) }
val requestBody = jsonPayload.toString().toRequestBody(JSON_CONTENT_TYPE) val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
val newHeaders = headersBuilder() val newHeaders = headersBuilder()
.add("Content-Length", requestBody.contentLength().toString()) .add("Content-Length", requestBody.contentLength().toString())
@ -173,44 +171,45 @@ class BilibiliComics : HttpSource() {
} }
override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply { override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply {
val jsonResponse = response.asJson().obj val result = json.decodeFromString<BilibiliResultDto<BilibiliComicDto>>(response.body!!.string())
val comic = result.data!!
title = jsonResponse["data"]["title"].string title = comic.title
author = jsonResponse["data"]["author_name"].array.joinToString { it.string } author = comic.authorName.joinToString()
status = if (jsonResponse["data"]["is_finish"].int == 1) SManga.COMPLETED else SManga.ONGOING status = if (comic.isFinish == 1) SManga.COMPLETED else SManga.ONGOING
genre = jsonResponse["data"]["styles"].array.joinToString { it.string } genre = comic.styles.joinToString()
description = jsonResponse["data"]["classic_lines"].string description = comic.classicLines
thumbnail_url = jsonResponse["data"]["vertical_cover"].string thumbnail_url = comic.verticalCover
} }
// Chapters are available in the same url of the manga details. // Chapters are available in the same url of the manga details.
override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga) override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val jsonResponse = response.asJson().obj val result = json.decodeFromString<BilibiliResultDto<BilibiliComicDto>>(response.body!!.string())
if (jsonResponse["code"].int != 0) if (result.code != 0)
return emptyList() return emptyList()
return jsonResponse["data"]["ep_list"].array return result.data!!.episodeList
.filter { ep -> ep["is_locked"].bool.not() } .filter { episode -> episode.isLocked.not() }
.map { ep -> chapterFromObject(ep, jsonResponse["data"]["id"].int) } .map { ep -> chapterFromObject(ep, result.data.id) }
} }
private fun chapterFromObject(obj: JsonElement, comicId: Int): SChapter = SChapter.create().apply { private fun chapterFromObject(episode: BilibiliEpisodeDto, comicId: Int): SChapter = SChapter.create().apply {
name = "Ep. " + obj["ord"].float.toString().removeSuffix(".0") + name = "Ep. " + episode.order.toString().removeSuffix(".0") +
" - " + obj["title"].string " - " + episode.title
chapter_number = obj["ord"].float chapter_number = episode.order
scanlator = this@BilibiliComics.name scanlator = this@BilibiliComics.name
date_upload = obj["pub_time"].string.substringBefore("T").toDate() date_upload = episode.publicationTime.substringBefore("T").toDate()
url = "/mc" + comicId + "/" + obj["id"].int url = "/mc$comicId/${episode.id}"
} }
override fun pageListRequest(chapter: SChapter): Request { override fun pageListRequest(chapter: SChapter): Request {
val chapterId = chapter.url.substringAfterLast("/").toInt() val chapterId = chapter.url.substringAfterLast("/").toInt()
val jsonPayload = jsonObject("ep_id" to chapterId) val jsonPayload = buildJsonObject { put("ep_id", chapterId) }
val requestBody = jsonPayload.toString().toRequestBody(JSON_CONTENT_TYPE) val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
val newHeaders = headersBuilder() val newHeaders = headersBuilder()
.add("Content-Length", requestBody.contentLength().toString()) .add("Content-Length", requestBody.contentLength().toString())
@ -226,19 +225,21 @@ class BilibiliComics : HttpSource() {
} }
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val jsonResponse = response.asJson().obj val result = json.decodeFromString<BilibiliResultDto<BilibiliReader>>(response.body!!.string())
if (jsonResponse["code"].int != 0) { if (result.code != 0) {
return emptyList() return emptyList()
} }
return jsonResponse["data"]["images"].array return result.data!!.images
.mapIndexed { i, page -> Page(i, page["path"].string, "") } .mapIndexed { i, page -> Page(i, page.path, "") }
} }
override fun imageUrlRequest(page: Page): Request { override fun imageUrlRequest(page: Page): Request {
val jsonPayload = jsonObject("urls" to jsonArray(page.url).toString()) val jsonPayload = buildJsonObject {
val requestBody = jsonPayload.toString().toRequestBody(JSON_CONTENT_TYPE) put("urls", buildJsonArray { add(page.url) }.toString())
}
val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
val newHeaders = headersBuilder() val newHeaders = headersBuilder()
.add("Content-Length", requestBody.contentLength().toString()) .add("Content-Length", requestBody.contentLength().toString())
@ -253,10 +254,10 @@ class BilibiliComics : HttpSource() {
} }
override fun imageUrlParse(response: Response): String { override fun imageUrlParse(response: Response): String {
val jsonResponse = response.asJson().obj val result = json.decodeFromString<BilibiliResultDto<List<BilibiliPageDto>>>(response.body!!.string())
val page = result.data!![0]
return jsonResponse["data"][0]["url"].string return "${page.url}?token=${page.token}"
.plus("?token=" + jsonResponse["data"][0]["token"].string)
} }
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used")
@ -271,14 +272,12 @@ class BilibiliComics : HttpSource() {
} }
} }
private fun Response.asJson(): JsonElement = JsonParser.parseString(body!!.string())
companion object { companion object {
private const val BASE_API_ENDPOINT = "twirp/comic.v1.Comic" private const val BASE_API_ENDPOINT = "twirp/comic.v1.Comic"
private const val ACCEPT_JSON = "application/json, text/plain, */*" private const val ACCEPT_JSON = "application/json, text/plain, */*"
private val JSON_CONTENT_TYPE = "application/json;charset=UTF-8".toMediaType() private val JSON_MEDIA_TYPE = "application/json;charset=UTF-8".toMediaType()
private const val FEATURED_ID = 3 private const val FEATURED_ID = 3

View File

@ -0,0 +1,59 @@
package eu.kanade.tachiyomi.extension.en.bilibilicomics
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class BilibiliResultDto<T>(
val code: Int = 0,
val data: T? = null,
@SerialName("msg") val message: String = ""
)
@Serializable
data class BilibiliFeaturedDto(
@SerialName("roll_six_comics") val rollSixComics: List<BilibiliComicDto> = emptyList()
)
@Serializable
data class BilibiliSearchDto(
val list: List<BilibiliComicDto> = emptyList()
)
@Serializable
data class BilibiliComicDto(
@SerialName("author_name") val authorName: List<String> = emptyList(),
@SerialName("classic_lines") val classicLines: String = "",
@SerialName("comic_id") val comicId: Int = 0,
@SerialName("ep_list") val episodeList: List<BilibiliEpisodeDto> = emptyList(),
val id: Int = 0,
@SerialName("is_finish") val isFinish: Int = 0,
val styles: List<String> = emptyList(),
val title: String,
@SerialName("vertical_cover") val verticalCover: String = ""
)
@Serializable
data class BilibiliEpisodeDto(
val id: Int,
@SerialName("is_locked") val isLocked: Boolean,
@SerialName("ord") val order: Float,
@SerialName("pub_time") val publicationTime: String,
val title: String
)
@Serializable
data class BilibiliReader(
val images: List<BilibiliImageDto> = emptyList()
)
@Serializable
data class BilibiliImageDto(
val path: String
)
@Serializable
data class BilibiliPageDto(
val token: String,
val url: String
)

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'NANI? Scans' extName = 'NANI? Scans'
pkgNameSuffix = 'en.naniscans' pkgNameSuffix = 'en.naniscans'
extClass = '.NaniScans' extClass = '.NaniScans'
extVersionCode = 5 extVersionCode = 6
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -8,59 +8,73 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.json.JSONArray
import org.json.JSONObject
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
class NaniScans : HttpSource() { class NaniScans : HttpSource() {
override val baseUrl = "https://naniscans.com"
override val lang = "en"
override val name = "NANI? Scans" override val name = "NANI? Scans"
override val baseUrl = "https://naniscans.com"
override val lang = "en"
override val supportsLatest = true override val supportsLatest = true
override val versionId = 2 override val versionId = 2
private val dateParser = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()) private val dateParser = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault())
private val json: Json by injectLazy()
override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page) override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page)
override fun latestUpdatesParse(response: Response): MangasPage { override fun latestUpdatesParse(response: Response): MangasPage {
val titlesJson = JSONArray(response.body!!.string()) val titlesJson = json.parseToJsonElement(response.body!!.string()).jsonArray
val mangaMap = mutableMapOf<Long, SManga>()
for (i in 0 until titlesJson.length()) { val mangaList = titlesJson
val manga = titlesJson.getJSONObject(i) .mapNotNull {
val manga = it.jsonObject
if (manga.getString("type") != "Comic") if (manga["type"]!!.jsonPrimitive.content != "Comic") {
continue return@mapNotNull null
}
var date = manga.getString("updatedAt") val date = manga["updatedAt"]!!.jsonPrimitive.content.let { datePrimitive ->
if (datePrimitive == "null") "2018-04-10T17:38:56" else datePrimitive
}
if (date == "null") Pair(dateParser.parse(date)!!.time, getBareSManga(manga))
date = "2018-04-10T17:38:56" }
.sortedByDescending { it.first }
.map { it.second }
mangaMap[dateParser.parse(date)!!.time] = getBareSManga(manga) return MangasPage(mangaList, false)
}
return MangasPage(mangaMap.toSortedMap().values.toList().asReversed(), false)
} }
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/api/titles") override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/api/titles")
override fun popularMangaParse(response: Response): MangasPage { override fun popularMangaParse(response: Response): MangasPage {
val titlesJson = JSONArray(response.body!!.string()) val titlesJson = json.parseToJsonElement(response.body!!.string()).jsonArray
val mangaList = mutableListOf<SManga>()
for (i in 0 until titlesJson.length()) { val mangaList = titlesJson.mapNotNull {
val manga = titlesJson.getJSONObject(i) val manga = it.jsonObject
if (manga.getString("type") != "Comic") if (manga["type"]!!.jsonPrimitive.content == "Comic") {
continue getBareSManga(manga)
} else {
mangaList.add(getBareSManga(manga)) null
}
} }
return MangasPage(mangaList, false) return MangasPage(mangaList, false)
@ -77,65 +91,54 @@ class NaniScans : HttpSource() {
override fun mangaDetailsRequest(manga: SManga) = GET("$baseUrl/titles/${manga.url}") override fun mangaDetailsRequest(manga: SManga) = GET("$baseUrl/titles/${manga.url}")
override fun mangaDetailsParse(response: Response): SManga { override fun mangaDetailsParse(response: Response): SManga {
val titleJson = JSONObject(response.body!!.string()) val titleJson = json.parseToJsonElement(response.body!!.string()).jsonObject
if (titleJson.getString("type") != "Comic") if (titleJson["type"]!!.jsonPrimitive.content != "Comic")
throw UnsupportedOperationException("Tachiyomi only supports Comics.") throw UnsupportedOperationException("Tachiyomi only supports Comics.")
return SManga.create().apply { return SManga.create().apply {
title = titleJson.getString("name") title = titleJson["name"]!!.jsonPrimitive.content
artist = titleJson.getString("artist") artist = titleJson["artist"]!!.jsonPrimitive.content
author = titleJson.getString("author") author = titleJson["author"]!!.jsonPrimitive.content
description = titleJson.getString("synopsis") description = titleJson["synopsis"]!!.jsonPrimitive.content
status = getStatus(titleJson.getString("status")) status = getStatus(titleJson["status"]!!.jsonPrimitive.content)
thumbnail_url = "$baseUrl${titleJson.getString("coverUrl")}" thumbnail_url = "$baseUrl${titleJson["coverUrl"]!!.jsonPrimitive.content}"
genre = titleJson.getJSONArray("tags").join(", ").replace("\"", "") genre = titleJson["tags"]!!.jsonArray.joinToString { it.jsonPrimitive.content }
url = titleJson.getString("id") url = titleJson["id"]!!.jsonPrimitive.content
} }
} }
override fun chapterListRequest(manga: SManga) = GET("$baseUrl/api/titles/${manga.url}") override fun chapterListRequest(manga: SManga) = GET("$baseUrl/api/titles/${manga.url}")
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val titleJson = JSONObject(response.body!!.string()) val titleJson = json.parseToJsonElement(response.body!!.string()).jsonObject
if (titleJson.getString("type") != "Comic") if (titleJson["type"]!!.jsonPrimitive.content != "Comic")
throw UnsupportedOperationException("Tachiyomi only supports Comics.") throw UnsupportedOperationException("Tachiyomi only supports Comics.")
val chaptersJson = titleJson.getJSONArray("chapters") val chaptersJson = titleJson["chapters"]!!.jsonArray
val chaptersList = mutableListOf<SChapter>()
for (i in 0 until chaptersJson.length()) { return titleJson["chapters"]!!.jsonArray.map {
val chapter = chaptersJson.getJSONObject(i) val chapter = it.jsonObject
chaptersList.add( SChapter.create().apply {
SChapter.create().apply { chapter_number = chapter["number"]!!.jsonPrimitive.content.toFloatOrNull() ?: -1f
chapter_number = chapter.get("number").toString().toFloat() name = getChapterTitle(chapter)
name = getChapterTitle(chapter) date_upload = dateParser.parse(chapter["releaseDate"]!!.jsonPrimitive.content)!!.time
date_upload = dateParser.parse(chapter.getString("releaseDate"))!!.time url = "${titleJson["id"]!!.jsonPrimitive.content} ${chapter["id"]!!.jsonPrimitive.content}"
url = "${titleJson.getString("id")}_${chapter.getString("id")}" }
}
)
} }
return chaptersList
} }
override fun pageListRequest(chapter: SChapter): Request = GET("$baseUrl/api/chapters/${chapter.url.substring(37, 73)}") override fun pageListRequest(chapter: SChapter): Request = GET("$baseUrl/api/chapters/${chapter.url.substring(37, 73)}")
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val jsonObject = JSONObject(response.body!!.string()) val jsonObject = json.parseToJsonElement(response.body!!.string()).jsonObject
val pagesJson = jsonObject.getJSONArray("pages") return jsonObject["pages"]!!.jsonArray.map {
val pagesList = mutableListOf<Page>() val item = it.jsonObject
Page(item["number"]!!.jsonPrimitive.int, "", "$baseUrl${item["pageUrl"]!!.jsonPrimitive.content}")
for (i in 0 until pagesJson.length()) {
val item = pagesJson.getJSONObject(i)
pagesList.add(Page(item.getInt("number"), "", "$baseUrl${item.getString("pageUrl")}"))
} }
return pagesList
} }
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not Used.") override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not Used.")
@ -146,23 +149,23 @@ class NaniScans : HttpSource() {
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
private fun getChapterTitle(chapter: JSONObject): String { private fun getChapterTitle(chapter: JsonObject): String {
val chapterName = mutableListOf<String>() val chapterName = mutableListOf<String>()
if (chapter.getString("volume") != "null") { if (chapter["volume"]!!.jsonPrimitive.content != "null") {
chapterName.add("Vol." + chapter.getString("volume")) chapterName.add("Vol." + chapter["volume"]!!.jsonPrimitive.content)
} }
if (chapter.getString("number") != "null") { if (chapter["number"]!!.jsonPrimitive.content != "null") {
chapterName.add("Ch." + chapter.getString("number")) chapterName.add("Ch." + chapter["number"]!!.jsonPrimitive.content)
} }
if (chapter.getString("name") != "null") { if (chapter["name"]!!.jsonPrimitive.content != "null") {
if (chapterName.isNotEmpty()) { if (chapterName.isNotEmpty()) {
chapterName.add("-") chapterName.add("-")
} }
chapterName.add(chapter.getString("name")) chapterName.add(chapter["name"]!!.jsonPrimitive.content)
} }
if (chapterName.isEmpty()) { if (chapterName.isEmpty()) {
@ -172,9 +175,9 @@ class NaniScans : HttpSource() {
return chapterName.joinToString(" ") return chapterName.joinToString(" ")
} }
private fun getBareSManga(manga: JSONObject): SManga = SManga.create().apply { private fun getBareSManga(manga: JsonObject): SManga = SManga.create().apply {
title = manga.getString("name") title = manga["name"]!!.jsonPrimitive.content
thumbnail_url = "$baseUrl${manga.getString("coverUrl")}" thumbnail_url = "$baseUrl${manga["coverUrl"]!!.jsonPrimitive.content}"
url = manga.getString("id") url = manga["id"]!!.jsonPrimitive.content
} }
} }