|
|
|
@ -3,12 +3,15 @@ package org.koitharu.kotatsu.parsers.site.ru.rulib
|
|
|
|
import androidx.collection.*
|
|
|
|
import androidx.collection.*
|
|
|
|
import kotlinx.coroutines.async
|
|
|
|
import kotlinx.coroutines.async
|
|
|
|
import kotlinx.coroutines.coroutineScope
|
|
|
|
import kotlinx.coroutines.coroutineScope
|
|
|
|
|
|
|
|
import kotlinx.coroutines.runBlocking
|
|
|
|
import okhttp3.HttpUrl
|
|
|
|
import okhttp3.HttpUrl
|
|
|
|
import org.json.JSONArray
|
|
|
|
import org.json.JSONArray
|
|
|
|
import org.json.JSONObject
|
|
|
|
import org.json.JSONObject
|
|
|
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
|
|
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
|
|
|
|
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
|
|
|
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
|
|
|
import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
|
|
|
|
import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
|
|
|
import org.koitharu.kotatsu.parsers.model.*
|
|
|
|
import org.koitharu.kotatsu.parsers.model.*
|
|
|
|
import org.koitharu.kotatsu.parsers.util.*
|
|
|
|
import org.koitharu.kotatsu.parsers.util.*
|
|
|
|
import org.koitharu.kotatsu.parsers.util.json.*
|
|
|
|
import org.koitharu.kotatsu.parsers.util.json.*
|
|
|
|
@ -19,9 +22,24 @@ import java.util.*
|
|
|
|
internal abstract class LibSocialParser(
|
|
|
|
internal abstract class LibSocialParser(
|
|
|
|
context: MangaLoaderContext,
|
|
|
|
context: MangaLoaderContext,
|
|
|
|
source: MangaParserSource,
|
|
|
|
source: MangaParserSource,
|
|
|
|
protected val siteDomain: String,
|
|
|
|
siteDomain: String,
|
|
|
|
protected val siteId: Int,
|
|
|
|
protected val siteId: Int,
|
|
|
|
) : LegacyPagedMangaParser(context, source, pageSize = 60) {
|
|
|
|
) : LegacyPagedMangaParser(context, source, pageSize = 60), MangaParserAuthProvider {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected val apiHost = "api.cdnlibs.org"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override val authUrl: String
|
|
|
|
|
|
|
|
get() = "https://$domain/ru/front/auth"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override val isAuthorized: Boolean
|
|
|
|
|
|
|
|
get() = runBlocking {
|
|
|
|
|
|
|
|
runCatchingCancellable { getAuthData() }.getOrNull() != null
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override suspend fun getUsername(): String = getAuthData()
|
|
|
|
|
|
|
|
?.getJSONObject("auth")
|
|
|
|
|
|
|
|
?.getString("username")
|
|
|
|
|
|
|
|
?: throw AuthRequiredException(source)
|
|
|
|
|
|
|
|
|
|
|
|
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
|
|
|
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
|
|
|
SortOrder.UPDATED,
|
|
|
|
SortOrder.UPDATED,
|
|
|
|
@ -70,8 +88,8 @@ internal abstract class LibSocialParser(
|
|
|
|
|
|
|
|
|
|
|
|
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
|
|
|
|
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
|
|
|
|
val urlBuilder = HttpUrl.Builder()
|
|
|
|
val urlBuilder = HttpUrl.Builder()
|
|
|
|
.scheme("https")
|
|
|
|
.scheme(SCHEME_HTTPS)
|
|
|
|
.host("api.lib.social")
|
|
|
|
.host(apiHost)
|
|
|
|
.addPathSegment("api")
|
|
|
|
.addPathSegment("api")
|
|
|
|
.addPathSegment("manga")
|
|
|
|
.addPathSegment("manga")
|
|
|
|
.addQueryParameter("site_id[]", siteId.toString())
|
|
|
|
.addQueryParameter("site_id[]", siteId.toString())
|
|
|
|
@ -126,8 +144,8 @@ internal abstract class LibSocialParser(
|
|
|
|
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
|
|
|
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
|
|
|
val chapters = async { fetchChapters(manga) }
|
|
|
|
val chapters = async { fetchChapters(manga) }
|
|
|
|
val url = HttpUrl.Builder()
|
|
|
|
val url = HttpUrl.Builder()
|
|
|
|
.scheme("https")
|
|
|
|
.scheme(SCHEME_HTTPS)
|
|
|
|
.host("api.lib.social")
|
|
|
|
.host(apiHost)
|
|
|
|
.addPathSegment("api")
|
|
|
|
.addPathSegment("api")
|
|
|
|
.addPathSegment("manga")
|
|
|
|
.addPathSegment("manga")
|
|
|
|
.addPathSegment(manga.url)
|
|
|
|
.addPathSegment(manga.url)
|
|
|
|
@ -157,7 +175,7 @@ internal abstract class LibSocialParser(
|
|
|
|
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> = coroutineScope {
|
|
|
|
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> = coroutineScope {
|
|
|
|
val pages = async {
|
|
|
|
val pages = async {
|
|
|
|
webClient.httpGet(
|
|
|
|
webClient.httpGet(
|
|
|
|
concatUrl("https://api.lib.social/api/manga/", chapter.url),
|
|
|
|
concatUrl("https://$apiHost/api/manga/", chapter.url),
|
|
|
|
).parseJson().getJSONObject("data")
|
|
|
|
).parseJson().getJSONObject("data")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
val servers = imageServers.get()
|
|
|
|
val servers = imageServers.get()
|
|
|
|
@ -183,8 +201,8 @@ internal abstract class LibSocialParser(
|
|
|
|
override suspend fun getRelatedManga(seed: Manga): List<Manga> {
|
|
|
|
override suspend fun getRelatedManga(seed: Manga): List<Manga> {
|
|
|
|
val json = webClient.httpGet(
|
|
|
|
val json = webClient.httpGet(
|
|
|
|
HttpUrl.Builder()
|
|
|
|
HttpUrl.Builder()
|
|
|
|
.scheme("https")
|
|
|
|
.scheme(SCHEME_HTTPS)
|
|
|
|
.host("api.lib.social")
|
|
|
|
.host(apiHost)
|
|
|
|
.addPathSegment("api")
|
|
|
|
.addPathSegment("api")
|
|
|
|
.addPathSegment("manga")
|
|
|
|
.addPathSegment("manga")
|
|
|
|
.addPathSegment(seed.url)
|
|
|
|
.addPathSegment(seed.url)
|
|
|
|
@ -196,6 +214,10 @@ internal abstract class LibSocialParser(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override suspend fun resolveLink(resolver: LinkResolver, link: HttpUrl): Manga? {
|
|
|
|
|
|
|
|
return resolver.resolveManga(this, link.pathSegments.lastOrNull() ?: return null)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
|
|
|
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
|
|
|
super.onCreateConfig(keys)
|
|
|
|
super.onCreateConfig(keys)
|
|
|
|
keys.remove(configKeyDomain)
|
|
|
|
keys.remove(configKeyDomain)
|
|
|
|
@ -211,7 +233,7 @@ internal abstract class LibSocialParser(
|
|
|
|
title = jo.getString("rus_name").ifEmpty { jo.getString("name") },
|
|
|
|
title = jo.getString("rus_name").ifEmpty { jo.getString("name") },
|
|
|
|
altTitles = setOfNotNull(jo.getString("name")),
|
|
|
|
altTitles = setOfNotNull(jo.getString("name")),
|
|
|
|
url = jo.getString("slug_url"),
|
|
|
|
url = jo.getString("slug_url"),
|
|
|
|
publicUrl = "https://$siteDomain/ru/manga/" + jo.getString("slug_url"),
|
|
|
|
publicUrl = "https://$domain/ru/manga/" + jo.getString("slug_url"),
|
|
|
|
rating = jo.optJSONObject("rating")
|
|
|
|
rating = jo.optJSONObject("rating")
|
|
|
|
?.getFloatOrDefault("average", RATING_UNKNOWN * 10f)?.div(10f) ?: RATING_UNKNOWN,
|
|
|
|
?.getFloatOrDefault("average", RATING_UNKNOWN * 10f)?.div(10f) ?: RATING_UNKNOWN,
|
|
|
|
contentRating = if (isNsfwSource) ContentRating.ADULT else null,
|
|
|
|
contentRating = if (isNsfwSource) ContentRating.ADULT else null,
|
|
|
|
@ -236,8 +258,8 @@ internal abstract class LibSocialParser(
|
|
|
|
|
|
|
|
|
|
|
|
private suspend fun fetchChapters(manga: Manga): List<MangaChapter> {
|
|
|
|
private suspend fun fetchChapters(manga: Manga): List<MangaChapter> {
|
|
|
|
val url = HttpUrl.Builder()
|
|
|
|
val url = HttpUrl.Builder()
|
|
|
|
.scheme("https")
|
|
|
|
.scheme(SCHEME_HTTPS)
|
|
|
|
.host("api.lib.social")
|
|
|
|
.host(apiHost)
|
|
|
|
.addPathSegment("api")
|
|
|
|
.addPathSegment("api")
|
|
|
|
.addPathSegment("manga")
|
|
|
|
.addPathSegment("manga")
|
|
|
|
.addPathSegment(manga.url)
|
|
|
|
.addPathSegment(manga.url)
|
|
|
|
@ -280,8 +302,8 @@ internal abstract class LibSocialParser(
|
|
|
|
private suspend fun fetchTags(type: String): List<MangaTag> {
|
|
|
|
private suspend fun fetchTags(type: String): List<MangaTag> {
|
|
|
|
val data = webClient.httpGet(
|
|
|
|
val data = webClient.httpGet(
|
|
|
|
HttpUrl.Builder()
|
|
|
|
HttpUrl.Builder()
|
|
|
|
.scheme("https")
|
|
|
|
.scheme(SCHEME_HTTPS)
|
|
|
|
.host("api.lib.social")
|
|
|
|
.host(apiHost)
|
|
|
|
.addPathSegment("api").addPathSegment(type).build(),
|
|
|
|
.addPathSegment("api").addPathSegment(type).build(),
|
|
|
|
).parseJson().getJSONArray("data")
|
|
|
|
).parseJson().getJSONArray("data")
|
|
|
|
val prefix = type.first().toString()
|
|
|
|
val prefix = type.first().toString()
|
|
|
|
@ -301,8 +323,8 @@ internal abstract class LibSocialParser(
|
|
|
|
private suspend fun fetchServers(): ScatterMap<String, String> {
|
|
|
|
private suspend fun fetchServers(): ScatterMap<String, String> {
|
|
|
|
val json = webClient.httpGet(
|
|
|
|
val json = webClient.httpGet(
|
|
|
|
HttpUrl.Builder()
|
|
|
|
HttpUrl.Builder()
|
|
|
|
.scheme("https")
|
|
|
|
.scheme(SCHEME_HTTPS)
|
|
|
|
.host("api.lib.social")
|
|
|
|
.host(apiHost)
|
|
|
|
.addPathSegment("api")
|
|
|
|
.addPathSegment("api")
|
|
|
|
.addPathSegment("constants")
|
|
|
|
.addPathSegment("constants")
|
|
|
|
.addQueryParameter("fields[]", "imageServers")
|
|
|
|
.addQueryParameter("fields[]", "imageServers")
|
|
|
|
@ -351,6 +373,10 @@ internal abstract class LibSocialParser(
|
|
|
|
return result
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private suspend fun getAuthData(): JSONObject? {
|
|
|
|
|
|
|
|
return JSONObject(WebViewHelper(context, domain).getLocalStorageValue("auth") ?: return null)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected companion object {
|
|
|
|
protected companion object {
|
|
|
|
|
|
|
|
|
|
|
|
const val SERVER_MAIN = "main"
|
|
|
|
const val SERVER_MAIN = "main"
|
|
|
|
|