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
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
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.SManga
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
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 okhttp3.OkHttpClient
class SilenceScan : WPMangaStream(
"Silence Scan",
@ -29,31 +26,33 @@ class SilenceScan : WPMangaStream(
.addNetworkInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS))
.build()
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
val infoEl = document.select("div.bigcontent, div.animefull").first()
private val json: Json by injectLazy()
author = infoEl.select("b:contains(Autor) + span").text()
artist = infoEl.select("b:contains(Artista) + span").text()
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
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())
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()
val genres = infoEl.select("b:contains(Gêneros) + span a")
.map { element -> element.text().toLowerCase() }
val genres = infoEl.select("span.mgen a[rel]")
.map { element -> element.text() }
.toMutableSet()
// add series type(manga/manhwa/manhua/other) thinggy to genre
document.select(seriesTypeSelector).firstOrNull()?.ownText()?.let {
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
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!!.isEmpty() -> 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 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 {
name = element.select("span.chapternum").text()
@ -73,24 +79,6 @@ class SilenceScan : WPMangaStream(
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(
Genre("4-koma", "4-koma"),
Genre("Ação", "acao"),
@ -126,4 +114,8 @@ class SilenceScan : WPMangaStream(
Genre("Violência sexual", "violencia-sexual"),
Genre("Yuri", "yuri")
)
companion object {
private val PORTUGUESE_LOCALE = Locale("pt", "BR")
}
}

View File

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

View File

@ -10,7 +10,7 @@ class FoolSlideGenerator : ThemeSourceGenerator {
override val themeClass = "FoolSlide"
override val baseVersionCode: Int = 1
override val baseVersionCode: Int = 2
override val sources = listOf(
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.Canvas
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.source.model.Filter
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.online.ParsedHttpSource
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.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Interceptor
@ -28,6 +27,7 @@ import okhttp3.ResponseBody.Companion.toResponseBody
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.text.ParseException
@ -57,6 +57,8 @@ abstract class GigaViewer(
.add("Origin", baseUrl)
.add("Referer", baseUrl)
private val json: Json by injectLazy()
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/series", headers)
override fun popularMangaSelector(): String = "ul.series-list li a"
@ -143,9 +145,12 @@ abstract class GigaViewer(
var result = client.newCall(request).execute()
while (result.code != 404) {
val json = result.asJsonObject()
readMoreEndpoint = json["nextUrl"].string
val tempDocument = Jsoup.parse(json["html"].string, response.request.url.toString())
val jsonResult = json.parseToJsonElement(result.body!!.string()).jsonObject
readMoreEndpoint = jsonResult["nextUrl"]!!.jsonPrimitive.content
val tempDocument = Jsoup.parse(
jsonResult["html"]!!.jsonPrimitive.content,
response.request.url.toString()
)
chapters += tempDocument
.select("ul.series-episode-list " + chapterListSelector())
@ -177,16 +182,16 @@ abstract class GigaViewer(
}
override fun pageListParse(document: Document): List<Page> {
val episodeJson = document.select("script#episode-json")
val episode = document.select("script#episode-json")
.attr("data-value")
.let { JsonParser.parseString(it).obj }
.let { json.decodeFromString<GigaViewerEpisodeDto>(it) }
return episodeJson["readableProduct"]["pageStructure"]["pages"].asJsonArray
.filter { it["type"].string == "main" }
.mapIndexed { i, pageObj ->
val imageUrl = pageObj["src"].string.toHttpUrlOrNull()!!.newBuilder()
.addQueryParameter("width", pageObj["width"].string)
.addQueryParameter("height", pageObj["height"].string)
return episode.readableProduct.pageStructure.pages
.filter { it.type == "main" }
.mapIndexed { i, page ->
val imageUrl = page.src.toHttpUrlOrNull()!!.newBuilder()
.addQueryParameter("width", page.width.toString())
.addQueryParameter("height", page.height.toString())
.toString()
Page(i, document.location(), imageUrl)
}
@ -280,8 +285,6 @@ abstract class GigaViewer(
}
}
private fun Response.asJsonObject(): JsonObject = JsonParser.parseString(body!!.string()).obj
companion object {
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 baseVersionCode: Int = 1
override val baseVersionCode: Int = 2
override val sources = listOf(
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.SManga
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.Request
import okhttp3.Response
import org.json.JSONArray
import org.json.JSONObject
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
@ -55,6 +60,8 @@ abstract class MangAdventure(
add("Referer", baseUrl)
}
private val json: Json by injectLazy()
override fun latestUpdatesRequest(page: Int) =
GET("$apiUrl/releases/", headers)
@ -100,58 +107,57 @@ abstract class MangAdventure(
}
override fun latestUpdatesParse(response: Response) =
JSONArray(response.asString()).run {
json.parseToJsonElement(response.asString()).jsonArray.run {
MangasPage(
(0 until length()).map {
val obj = getJSONObject(it)
map {
val obj = it.jsonObject
SManga.create().apply {
url = obj.getString("url")
title = obj.getString("title")
thumbnail_url = obj.getString("cover")
// A bit of a hack to sort by date
url = obj["url"]!!.jsonPrimitive.content
title = obj["title"]!!.jsonPrimitive.content
thumbnail_url = obj["cover"]!!.jsonPrimitive.content
description = httpDateToTimestamp(
obj.getJSONObject("latest_chapter").getString("date")
obj["latest_chapter"]!!.jsonObject["date"]!!.jsonPrimitive.content
).toString()
}
}.sortedByDescending(SManga::description),
}
.sortedByDescending(SManga::description),
false
)
}
override fun chapterListParse(response: Response) =
JSONObject(response.asString()).getJSONObject("volumes").run {
keys().asSequence().flatMap { vol ->
val chapters = getJSONObject(vol)
chapters.keys().asSequence().map { ch ->
json.parseToJsonElement(response.asString()).jsonObject["volumes"]!!.jsonObject.entries
.reversed()
.flatMap { vol ->
vol.value.jsonObject.entries.map { ch ->
SChapter.create().fromJSON(
chapters.getJSONObject(ch).also {
it.put("volume", vol)
it.put("chapter", ch)
ch.value.jsonObject.toMutableMap().let {
it["volume"] = JsonPrimitive(vol.key)
it["chapter"] = JsonPrimitive(ch.key)
JsonObject(it)
}
)
}
}.toList().reversed()
}
}
override fun mangaDetailsParse(response: Response) =
SManga.create().fromJSON(JSONObject(response.asString()))
SManga.create().fromJSON(json.parseToJsonElement(response.asString()).jsonObject)
override fun pageListParse(response: Response) =
JSONObject(response.asString()).run {
val url = getString("url")
val root = getString("pages_root")
val arr = getJSONArray("pages_list")
(0 until arr.length()).map {
Page(it, "$url${it + 1}", "$root${arr.getString(it)}")
json.parseToJsonElement(response.asString()).jsonObject.run {
val url = get("url")!!.jsonPrimitive.content
val root = get("pages_root")!!.jsonPrimitive.content
get("pages_list")!!.jsonArray.mapIndexed { i, jsonEl ->
Page(i, "$url${i + 1}", "$root${jsonEl.jsonPrimitive.content}")
}
}
override fun searchMangaParse(response: Response) =
JSONArray(response.asString()).run {
json.parseToJsonElement(response.asString()).jsonArray.run {
MangasPage(
(0 until length()).map {
SManga.create().fromJSON(getJSONObject(it))
}.sortedBy(SManga::title),
map { SManga.create().fromJSON(it.jsonObject) },
false
)
}

View File

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

View File

@ -9,7 +9,7 @@ class MangAdventureGenerator : ThemeSourceGenerator {
override val themeClass = "MangAdventure"
override val baseVersionCode = 1
override val baseVersionCode = 2
override val sources = listOf(
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.online.ParsedHttpSource
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.CookieJar
import okhttp3.Headers
@ -19,10 +22,10 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.json.JSONObject
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
@ -76,6 +79,8 @@ open class Webtoons(
}
}
private val json: Json by injectLazy()
override fun popularMangaSelector() = "not using"
override fun latestUpdatesSelector() = "div#dailyList > $day li > a"
@ -262,14 +267,16 @@ open class Webtoons(
val docUrl = docUrlRegex.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 keys.mapIndexed { i, key ->
Page(i, "", motiontoonPath + motiontoonJson.getString(key))
}
return motiontoonImages.entries
.filter { it.key.contains("layer") }
.mapIndexed { i, entry ->
Page(i, "", motiontoonPath + entry.value.jsonPrimitive.content)
}
}
companion object {

View File

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

View File

@ -9,7 +9,7 @@ class WebtoonsTranslateGenerator : ThemeSourceGenerator {
override val themeClass = "WebtoonsTranslation"
override val baseVersionCode: Int = 1
override val baseVersionCode: Int = 2
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),

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.multisrc.wpmangareader
import android.util.Log
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
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.online.ParsedHttpSource
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.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.json.JSONArray
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import rx.Single
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
@ -36,6 +38,8 @@ abstract class WPMangaReader(
override val client: OkHttpClient = network.cloudflareClient
private val json: Json by injectLazy()
// popular
override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", FilterList(OrderByFilter(5)))
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
*/
protected open fun mangaIdFromUrl(s: String): Single<String?> {
var baseMangaUrl = "$baseUrl$mangaUrlDirectory".toHttpUrlOrNull()!!
val baseMangaUrl = "$baseUrl$mangaUrlDirectory".toHttpUrlOrNull()!!
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())
val isMangaUrl = listOf(
@ -226,7 +230,7 @@ abstract class WPMangaReader(
open val pageSelector = "div#readerarea img"
override fun pageListParse(document: Document): List<Page> {
var pages = mutableListOf<Page>()
val pages = mutableListOf<Page>()
document.select(pageSelector)
.filterNot { it.attr("src").isNullOrEmpty() }
.mapIndexed { i, img -> pages.add(Page(i, "", img.attr("abs:src"))) }
@ -236,11 +240,12 @@ abstract class WPMangaReader(
val docString = document.toString()
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.add(Page(i, "", imageList.getString(i)))
pages += imageList.mapIndexed { i, jsonEl ->
Page(i, "", jsonEl.jsonPrimitive.content)
}
return pages

View File

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

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.multisrc.wpmangastream
//import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor // added to override
import android.app.Application
import android.content.SharedPreferences
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.online.ParsedHttpSource
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.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.json.JSONArray
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
@ -67,17 +69,16 @@ abstract class WPMangaStream(
private fun getShowThumbnail(): Int = preferences.getInt(SHOW_THUMBNAIL_PREF, 0)
//private val rateLimitInterceptor = RateLimitInterceptor(4)
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
//.addNetworkInterceptor(rateLimitInterceptor)
.build()
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()
private val json: Json by injectLazy()
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/manga/?page=$page&order=popular", headers)
}
@ -167,7 +168,7 @@ abstract class WPMangaStream(
// add alternative name to manga description
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!!.isEmpty() -> 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 altName = "Alternative Name" + ": "
protected fun parseStatus(element: String?): Int = when {
protected open fun parseStatus(element: String?): Int = when {
element == null -> SManga.UNKNOWN
listOf("ongoing", "publishing").any { it.contains(element, ignoreCase = true) } -> SManga.ONGOING
listOf("completed").any { it.contains(element, ignoreCase = true) } -> SManga.COMPLETED
@ -265,24 +266,26 @@ abstract class WPMangaStream(
open val pageSelector = "div#readerarea img"
override fun pageListParse(document: Document): List<Page> {
var pages = mutableListOf<Page>()
document.select(pageSelector)
val htmlPages = document.select(pageSelector)
.filterNot { it.attr("src").isNullOrEmpty() }
.mapIndexed { i, img -> pages.add(Page(i, "", img.attr("abs:src"))) }
// Some wpmangastream sites now load pages via javascript
if (pages.isNotEmpty()) { return pages }
.mapIndexed { i, img -> Page(i, "", img.attr("abs:src")) }
.toMutableList()
val docString = document.toString()
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.add(Page(i, "", imageList.getString(i)))
val scriptPages = imageList.mapIndexed { i, jsonEl ->
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")

View File

@ -9,10 +9,10 @@ class WPMangaStreamGenerator : ThemeSourceGenerator {
override val themeClass = "WPMangaStream"
override val baseVersionCode: Int = 4
override val baseVersionCode: Int = 5
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("MasterKomik", "https://masterkomik.com", "id"),
SingleLang("Kaisar Komik", "https://kaisarkomik.com", "id"),
@ -34,7 +34,7 @@ class WPMangaStreamGenerator : ThemeSourceGenerator {
SingleLang("MangaSwat", "https://mangaswat.com", "ar"),
SingleLang("Manga Raw.org", "https://mangaraw.org", "ja", className = "MangaRawOrg", overrideVersionCode = 1),
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("Tempest Manga", "https://manga.tempestfansub.com", "tr"),
SingleLang("xCaliBR Scans", "https://xcalibrscans.com", "en", overrideVersionCode = 2),

View File

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

View File

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

View File

@ -1,16 +1,5 @@
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.lib.ratelimit.RateLimitInterceptor
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.SManga
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.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
@ -30,6 +25,7 @@ import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.jsoup.Jsoup
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
@ -55,14 +51,16 @@ class BilibiliComics : HttpSource() {
.add("Origin", baseUrl)
.add("Referer", "$baseUrl/")
private val json: Json by injectLazy()
override fun popularMangaRequest(page: Int): Request {
val requestPayload = jsonObject(
"id" to FEATURED_ID,
"isAll" to 0,
"page_num" to 1,
"page_size" to 6
)
val requestBody = requestPayload.toString().toRequestBody(JSON_CONTENT_TYPE)
val requestPayload = buildJsonObject {
put("id", FEATURED_ID)
put("isAll", 0)
put("page_num", 1)
put("page_size", 6)
}
val requestBody = requestPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
val newHeaders = headersBuilder()
.add("Content-Length", requestBody.contentLength().toString())
@ -77,36 +75,36 @@ class BilibiliComics : HttpSource() {
}
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)
}
val comicList = jsonResponse["data"]["roll_six_comics"].array
val comicList = result.data!!.rollSixComics
.map(::popularMangaFromObject)
return MangasPage(comicList, hasNextPage = false)
}
private fun popularMangaFromObject(obj: JsonElement): SManga = SManga.create().apply {
title = obj["title"].string
thumbnail_url = obj["vertical_cover"].string
url = "/detail/mc" + obj["comic_id"].int
private fun popularMangaFromObject(comic: BilibiliComicDto): SManga = SManga.create().apply {
title = comic.title
thumbnail_url = comic.verticalCover
url = "/detail/mc${comic.comicId}"
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val jsonPayload = jsonObject(
"area_id" to -1,
"is_finish" to -1,
"is_free" to 1,
"key_word" to query,
"order" to 0,
"page_num" to page,
"page_size" to 9,
"style_id" to -1
)
val requestBody = jsonPayload.toString().toRequestBody(JSON_CONTENT_TYPE)
val jsonPayload = buildJsonObject {
put("area_id", -1)
put("is_finish", -1)
put("is_free", 1)
put("key_word", query)
put("order", 0)
put("page_num", page)
put("page_size", 9)
put("style_id", -1)
}
val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
val refererUrl = "$baseUrl/search".toHttpUrl().newBuilder()
.addQueryParameter("keyword", query)
@ -126,22 +124,22 @@ class BilibiliComics : HttpSource() {
}
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)
}
val comicList = jsonResponse["data"]["list"].array
val comicList = result.data!!.list
.map(::searchMangaFromObject)
return MangasPage(comicList, hasNextPage = false)
}
private fun searchMangaFromObject(obj: JsonElement): SManga = SManga.create().apply {
title = Jsoup.parse(obj["title"].string).text()
thumbnail_url = obj["vertical_cover"].string
url = "/detail/mc" + obj["id"].int
private fun searchMangaFromObject(comic: BilibiliComicDto): SManga = SManga.create().apply {
title = Jsoup.parse(comic.title).text()
thumbnail_url = comic.verticalCover
url = "/detail/mc${comic.id}"
}
// Workaround to allow "Open in browser" use the real URL.
@ -156,8 +154,8 @@ class BilibiliComics : HttpSource() {
private fun mangaDetailsApiRequest(manga: SManga): Request {
val comicId = manga.url.substringAfterLast("/mc").toInt()
val jsonPayload = jsonObject("comic_id" to comicId)
val requestBody = jsonPayload.toString().toRequestBody(JSON_CONTENT_TYPE)
val jsonPayload = buildJsonObject { put("comic_id", comicId) }
val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
val newHeaders = headersBuilder()
.add("Content-Length", requestBody.contentLength().toString())
@ -173,44 +171,45 @@ class BilibiliComics : HttpSource() {
}
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
author = jsonResponse["data"]["author_name"].array.joinToString { it.string }
status = if (jsonResponse["data"]["is_finish"].int == 1) SManga.COMPLETED else SManga.ONGOING
genre = jsonResponse["data"]["styles"].array.joinToString { it.string }
description = jsonResponse["data"]["classic_lines"].string
thumbnail_url = jsonResponse["data"]["vertical_cover"].string
title = comic.title
author = comic.authorName.joinToString()
status = if (comic.isFinish == 1) SManga.COMPLETED else SManga.ONGOING
genre = comic.styles.joinToString()
description = comic.classicLines
thumbnail_url = comic.verticalCover
}
// Chapters are available in the same url of the manga details.
override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga)
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 jsonResponse["data"]["ep_list"].array
.filter { ep -> ep["is_locked"].bool.not() }
.map { ep -> chapterFromObject(ep, jsonResponse["data"]["id"].int) }
return result.data!!.episodeList
.filter { episode -> episode.isLocked.not() }
.map { ep -> chapterFromObject(ep, result.data.id) }
}
private fun chapterFromObject(obj: JsonElement, comicId: Int): SChapter = SChapter.create().apply {
name = "Ep. " + obj["ord"].float.toString().removeSuffix(".0") +
" - " + obj["title"].string
chapter_number = obj["ord"].float
private fun chapterFromObject(episode: BilibiliEpisodeDto, comicId: Int): SChapter = SChapter.create().apply {
name = "Ep. " + episode.order.toString().removeSuffix(".0") +
" - " + episode.title
chapter_number = episode.order
scanlator = this@BilibiliComics.name
date_upload = obj["pub_time"].string.substringBefore("T").toDate()
url = "/mc" + comicId + "/" + obj["id"].int
date_upload = episode.publicationTime.substringBefore("T").toDate()
url = "/mc$comicId/${episode.id}"
}
override fun pageListRequest(chapter: SChapter): Request {
val chapterId = chapter.url.substringAfterLast("/").toInt()
val jsonPayload = jsonObject("ep_id" to chapterId)
val requestBody = jsonPayload.toString().toRequestBody(JSON_CONTENT_TYPE)
val jsonPayload = buildJsonObject { put("ep_id", chapterId) }
val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
val newHeaders = headersBuilder()
.add("Content-Length", requestBody.contentLength().toString())
@ -226,19 +225,21 @@ class BilibiliComics : HttpSource() {
}
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 jsonResponse["data"]["images"].array
.mapIndexed { i, page -> Page(i, page["path"].string, "") }
return result.data!!.images
.mapIndexed { i, page -> Page(i, page.path, "") }
}
override fun imageUrlRequest(page: Page): Request {
val jsonPayload = jsonObject("urls" to jsonArray(page.url).toString())
val requestBody = jsonPayload.toString().toRequestBody(JSON_CONTENT_TYPE)
val jsonPayload = buildJsonObject {
put("urls", buildJsonArray { add(page.url) }.toString())
}
val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
val newHeaders = headersBuilder()
.add("Content-Length", requestBody.contentLength().toString())
@ -253,10 +254,10 @@ class BilibiliComics : HttpSource() {
}
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
.plus("?token=" + jsonResponse["data"][0]["token"].string)
return "${page.url}?token=${page.token}"
}
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 {
private const val BASE_API_ENDPOINT = "twirp/comic.v1.Comic"
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

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