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:
parent
d6365b5910
commit
6d144464c7
|
@ -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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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) }
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
|
@ -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"),
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 -----------------
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
|
@ -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'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue