[LibSocial] Fixes

master
Koitharu 11 months ago
parent f066be47fe
commit 5856681753
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -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.*
@ -17,346 +20,369 @@ import java.text.SimpleDateFormat
import java.util.* 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 {
override val availableSortOrders: Set<SortOrder> = EnumSet.of( protected val apiHost = "api.cdnlibs.org"
SortOrder.UPDATED,
SortOrder.POPULARITY, override val authUrl: String
SortOrder.RATING, get() = "https://$domain/ru/front/auth"
SortOrder.NEWEST,
SortOrder.ALPHABETICAL, override val isAuthorized: Boolean
SortOrder.ALPHABETICAL_DESC, get() = runBlocking {
) runCatchingCancellable { getAuthData() }.getOrNull() != null
}
final override val configKeyDomain = ConfigKey.Domain(siteDomain)
override suspend fun getUsername(): String = getAuthData()
override val filterCapabilities: MangaListFilterCapabilities ?.getJSONObject("auth")
get() = MangaListFilterCapabilities( ?.getString("username")
isMultipleTagsSupported = true, ?: throw AuthRequiredException(source)
isTagsExclusionSupported = true,
isSearchSupported = true, override val availableSortOrders: Set<SortOrder> = EnumSet.of(
isSearchWithFiltersSupported = true, SortOrder.UPDATED,
) SortOrder.POPULARITY,
SortOrder.RATING,
override suspend fun getFilterOptions() = MangaListFilterOptions( SortOrder.NEWEST,
availableTags = fetchAvailableTags(), SortOrder.ALPHABETICAL,
availableStates = EnumSet.allOf(MangaState::class.java), SortOrder.ALPHABETICAL_DESC,
) )
private val statesMap = intObjectMapOf( final override val configKeyDomain = ConfigKey.Domain(siteDomain)
1, MangaState.ONGOING,
2, MangaState.FINISHED, override val filterCapabilities: MangaListFilterCapabilities
3, MangaState.UPCOMING, get() = MangaListFilterCapabilities(
4, MangaState.PAUSED, isMultipleTagsSupported = true,
5, MangaState.ABANDONED, isTagsExclusionSupported = true,
) isSearchSupported = true,
private val imageServers = suspendLazy(initializer = ::fetchServers) isSearchWithFiltersSupported = true,
private val splitTranslationsKey = ConfigKey.SplitByTranslations(true) )
private val preferredServerKey = ConfigKey.PreferredImageServer(
presetValues = mapOf( override suspend fun getFilterOptions() = MangaListFilterOptions(
null to null, availableTags = fetchAvailableTags(),
SERVER_MAIN to "Первый", availableStates = EnumSet.allOf(MangaState::class.java),
SERVER_SECONDARY to "Второй", )
SERVER_COMPRESS to "Сжатия",
SERVER_DOWNLOAD to "Загрузки", private val statesMap = intObjectMapOf(
SERVER_CROP to "Обрезки", 1, MangaState.ONGOING,
), 2, MangaState.FINISHED,
defaultValue = null, 3, MangaState.UPCOMING,
) 4, MangaState.PAUSED,
5, MangaState.ABANDONED,
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { )
val urlBuilder = HttpUrl.Builder() private val imageServers = suspendLazy(initializer = ::fetchServers)
.scheme("https") private val splitTranslationsKey = ConfigKey.SplitByTranslations(true)
.host("api.lib.social") private val preferredServerKey = ConfigKey.PreferredImageServer(
.addPathSegment("api") presetValues = mapOf(
.addPathSegment("manga") null to null,
.addQueryParameter("site_id[]", siteId.toString()) SERVER_MAIN to "Первый",
.addQueryParameter("fields[]", "rate") SERVER_SECONDARY to "Второй",
.addQueryParameter("fields[]", "rate_avg") SERVER_COMPRESS to "Сжатия",
.addQueryParameter("page", page.toString()) SERVER_DOWNLOAD to "Загрузки",
for (state in filter.states) { SERVER_CROP to "Обрезки",
urlBuilder.addQueryParameter("status[]", statesMap.keyOf(state).toString()) ),
} defaultValue = null,
for (tag in filter.tags) { )
urlBuilder.addQueryParameter("${tag.typeKey()}[]", tag.key.drop(1))
} override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
for (tag in filter.tagsExclude) { val urlBuilder = HttpUrl.Builder()
urlBuilder.addQueryParameter("${tag.typeKey()}_exclude[]", tag.key.drop(1)) .scheme(SCHEME_HTTPS)
} .host(apiHost)
if (!filter.query.isNullOrEmpty()) { .addPathSegment("api")
urlBuilder.addQueryParameter("q", filter.query) .addPathSegment("manga")
} .addQueryParameter("site_id[]", siteId.toString())
urlBuilder.addQueryParameter( .addQueryParameter("fields[]", "rate")
"sort_by", .addQueryParameter("fields[]", "rate_avg")
when (order) { .addQueryParameter("page", page.toString())
SortOrder.UPDATED -> "last_chapter_at" for (state in filter.states) {
SortOrder.POPULARITY -> "views" urlBuilder.addQueryParameter("status[]", statesMap.keyOf(state).toString())
SortOrder.RATING -> "rate_avg" }
SortOrder.NEWEST -> "created_at" for (tag in filter.tags) {
SortOrder.ALPHABETICAL, urlBuilder.addQueryParameter("${tag.typeKey()}[]", tag.key.drop(1))
SortOrder.ALPHABETICAL_DESC, }
-> "rus_name" for (tag in filter.tagsExclude) {
urlBuilder.addQueryParameter("${tag.typeKey()}_exclude[]", tag.key.drop(1))
else -> null }
}, if (!filter.query.isNullOrEmpty()) {
) urlBuilder.addQueryParameter("q", filter.query)
urlBuilder.addQueryParameter( }
"sort_type", urlBuilder.addQueryParameter(
when (order) { "sort_by",
SortOrder.UPDATED, when (order) {
SortOrder.POPULARITY, SortOrder.UPDATED -> "last_chapter_at"
SortOrder.RATING, SortOrder.POPULARITY -> "views"
SortOrder.NEWEST, SortOrder.RATING -> "rate_avg"
SortOrder.ALPHABETICAL_DESC, SortOrder.NEWEST -> "created_at"
-> "desc" SortOrder.ALPHABETICAL,
SortOrder.ALPHABETICAL_DESC,
SortOrder.ALPHABETICAL -> "asc" -> "rus_name"
else -> null
}, else -> null
) },
val json = webClient.httpGet(urlBuilder.build()).parseJson() )
val data = json.getJSONArray("data") urlBuilder.addQueryParameter(
return data.mapJSON(::parseManga) "sort_type",
} when (order) {
SortOrder.UPDATED,
override suspend fun getDetails(manga: Manga): Manga = coroutineScope { SortOrder.POPULARITY,
val chapters = async { fetchChapters(manga) } SortOrder.RATING,
val url = HttpUrl.Builder() SortOrder.NEWEST,
.scheme("https") SortOrder.ALPHABETICAL_DESC,
.host("api.lib.social") -> "desc"
.addPathSegment("api")
.addPathSegment("manga") SortOrder.ALPHABETICAL -> "asc"
.addPathSegment(manga.url) else -> null
.addQueryParameter("fields[]", "summary") },
.addQueryParameter("fields[]", "genres") )
.addQueryParameter("fields[]", "tags") val json = webClient.httpGet(urlBuilder.build()).parseJson()
.addQueryParameter("fields[]", "authors") val data = json.getJSONArray("data")
.build() return data.mapJSON(::parseManga)
val json = webClient.httpGet(url).parseJson().getJSONObject("data") }
val genres = json.getJSONArray("genres").mapJSON { jo ->
MangaTag(title = jo.getString("name"), key = "g" + jo.getInt("id"), source = source) override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
} val chapters = async { fetchChapters(manga) }
val tags = json.getJSONArray("genres").mapJSON { jo -> val url = HttpUrl.Builder()
MangaTag(title = jo.getString("name"), key = "t" + jo.getInt("id"), source = source) .scheme(SCHEME_HTTPS)
} .host(apiHost)
val author = json.getJSONArray("authors").optJSONObject(0)?.getStringOrNull("name") .addPathSegment("api")
manga.copy( .addPathSegment("manga")
title = json.getStringOrNull("rus_name") ?: manga.title, .addPathSegment(manga.url)
altTitles = setOfNotNull(json.getStringOrNull("name")), .addQueryParameter("fields[]", "summary")
tags = tagsSetOf(tags, genres), .addQueryParameter("fields[]", "genres")
authors = setOfNotNull(author), .addQueryParameter("fields[]", "tags")
description = json.getString("summary").nl2br(), .addQueryParameter("fields[]", "authors")
chapters = chapters.await(), .build()
) val json = webClient.httpGet(url).parseJson().getJSONObject("data")
} val genres = json.getJSONArray("genres").mapJSON { jo ->
MangaTag(title = jo.getString("name"), key = "g" + jo.getInt("id"), source = source)
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> = coroutineScope { }
val pages = async { val tags = json.getJSONArray("genres").mapJSON { jo ->
webClient.httpGet( MangaTag(title = jo.getString("name"), key = "t" + jo.getInt("id"), source = source)
concatUrl("https://api.lib.social/api/manga/", chapter.url), }
).parseJson().getJSONObject("data") val author = json.getJSONArray("authors").optJSONObject(0)?.getStringOrNull("name")
} manga.copy(
val servers = imageServers.get() title = json.getStringOrNull("rus_name") ?: manga.title,
val json = pages.await() altTitles = setOfNotNull(json.getStringOrNull("name")),
val primaryServer = getPrimaryImageServer(servers) tags = tagsSetOf(tags, genres),
json.getJSONArray("pages").mapJSON { jo -> authors = setOfNotNull(author),
val url = jo.getString("url") description = json.getString("summary").nl2br(),
MangaPage( chapters = chapters.await(),
id = generateUid(jo.getLong("id")), )
url = concatUrl(primaryServer, url), }
preview = servers[SERVER_COMPRESS]?.let { concatUrl(it, url) },
source = source, override suspend fun getPages(chapter: MangaChapter): List<MangaPage> = coroutineScope {
) val pages = async {
} webClient.httpGet(
} concatUrl("https://$apiHost/api/manga/", chapter.url),
).parseJson().getJSONObject("data")
private suspend fun fetchAvailableTags(): Set<MangaTag> = coroutineScope { }
val tags = async { fetchTags("tags") } val servers = imageServers.get()
val genres = async { fetchTags("genres") } val json = pages.await()
tagsSetOf(tags.await(), genres.await()) val primaryServer = getPrimaryImageServer(servers)
} json.getJSONArray("pages").mapJSON { jo ->
val url = jo.getString("url")
override suspend fun getRelatedManga(seed: Manga): List<Manga> { MangaPage(
val json = webClient.httpGet( id = generateUid(jo.getLong("id")),
HttpUrl.Builder() url = concatUrl(primaryServer, url),
.scheme("https") preview = servers[SERVER_COMPRESS]?.let { concatUrl(it, url) },
.host("api.lib.social") source = source,
.addPathSegment("api") )
.addPathSegment("manga") }
.addPathSegment(seed.url) }
.addPathSegment("similar")
.build(), private suspend fun fetchAvailableTags(): Set<MangaTag> = coroutineScope {
).parseJson().getJSONArray("data") val tags = async { fetchTags("tags") }
return json.mapJSON { jo -> val genres = async { fetchTags("genres") }
parseManga(jo.getJSONObject("media")) tagsSetOf(tags.await(), genres.await())
} }
}
override suspend fun getRelatedManga(seed: Manga): List<Manga> {
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { val json = webClient.httpGet(
super.onCreateConfig(keys) HttpUrl.Builder()
keys.remove(configKeyDomain) .scheme(SCHEME_HTTPS)
keys.add(splitTranslationsKey) .host(apiHost)
keys.add(preferredServerKey) .addPathSegment("api")
} .addPathSegment("manga")
.addPathSegment(seed.url)
private fun parseManga(jo: JSONObject): Manga { .addPathSegment("similar")
val cover = jo.getJSONObject("cover") .build(),
val isNsfwSource = jo.getJSONObject("ageRestriction").getIntOrDefault("id", 0) >= 3 ).parseJson().getJSONArray("data")
return Manga( return json.mapJSON { jo ->
id = generateUid(jo.getLong("id")), parseManga(jo.getJSONObject("media"))
title = jo.getString("rus_name").ifEmpty { jo.getString("name") }, }
altTitles = setOfNotNull(jo.getString("name")), }
url = jo.getString("slug_url"),
publicUrl = "https://$siteDomain/ru/manga/" + jo.getString("slug_url"), override suspend fun resolveLink(resolver: LinkResolver, link: HttpUrl): Manga? {
rating = jo.optJSONObject("rating") return resolver.resolveManga(this, link.pathSegments.lastOrNull() ?: return null)
?.getFloatOrDefault("average", RATING_UNKNOWN * 10f)?.div(10f) ?: RATING_UNKNOWN, }
contentRating = if (isNsfwSource) ContentRating.ADULT else null,
coverUrl = cover.getString("thumbnail"), override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
tags = setOf(), super.onCreateConfig(keys)
state = statesMap[jo.optJSONObject("status")?.getIntOrDefault("id", -1) ?: -1], keys.remove(configKeyDomain)
authors = emptySet(), keys.add(splitTranslationsKey)
largeCoverUrl = cover.getString("default"), keys.add(preferredServerKey)
source = source, }
)
} private fun parseManga(jo: JSONObject): Manga {
val cover = jo.getJSONObject("cover")
private fun getPrimaryImageServer(servers: ScatterMap<String, String>): String { val isNsfwSource = jo.getJSONObject("ageRestriction").getIntOrDefault("id", 0) >= 3
val preferred = config[preferredServerKey] return Manga(
if (preferred != null) { id = generateUid(jo.getLong("id")),
servers[preferred]?.let { return it } title = jo.getString("rus_name").ifEmpty { jo.getString("name") },
} altTitles = setOfNotNull(jo.getString("name")),
return checkNotNull(servers[SERVER_MAIN] ?: servers[SERVER_DOWNLOAD] ?: servers[SERVER_SECONDARY]) { url = jo.getString("slug_url"),
"No available images servers" publicUrl = "https://$domain/ru/manga/" + jo.getString("slug_url"),
} rating = jo.optJSONObject("rating")
} ?.getFloatOrDefault("average", RATING_UNKNOWN * 10f)?.div(10f) ?: RATING_UNKNOWN,
contentRating = if (isNsfwSource) ContentRating.ADULT else null,
private suspend fun fetchChapters(manga: Manga): List<MangaChapter> { coverUrl = cover.getString("thumbnail"),
val url = HttpUrl.Builder() tags = setOf(),
.scheme("https") state = statesMap[jo.optJSONObject("status")?.getIntOrDefault("id", -1) ?: -1],
.host("api.lib.social") authors = emptySet(),
.addPathSegment("api") largeCoverUrl = cover.getString("default"),
.addPathSegment("manga") source = source,
.addPathSegment(manga.url) )
.addPathSegment("chapters") }
.build()
val json = webClient.httpGet(url).parseJson().getJSONArray("data") private fun getPrimaryImageServer(servers: ScatterMap<String, String>): String {
val builder = ChaptersListBuilder(json.length()) val preferred = config[preferredServerKey]
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US) if (preferred != null) {
val useBranching = config[splitTranslationsKey] servers[preferred]?.let { return it }
for (i in 0 until json.length()) { }
val jo = json.getJSONObject(i) return checkNotNull(servers[SERVER_MAIN] ?: servers[SERVER_DOWNLOAD] ?: servers[SERVER_SECONDARY]) {
val volume = jo.getIntOrDefault("volume", 0) "No available images servers"
val number = jo.getFloatOrDefault("number", 0f) }
val numberString = number.formatSimple() }
val name = jo.getStringOrNull("name") ?: buildString {
if (volume > 0) append("Том ").append(volume).append(' ') private suspend fun fetchChapters(manga: Manga): List<MangaChapter> {
append("Глава ").append(numberString) val url = HttpUrl.Builder()
} .scheme(SCHEME_HTTPS)
val branches = jo.getJSONArray("branches") .host(apiHost)
for (j in 0 until branches.length()) { .addPathSegment("api")
val bjo = branches.getJSONObject(j) .addPathSegment("manga")
val id = bjo.getLong("id") .addPathSegment(manga.url)
val team = bjo.getJSONArray("teams").optJSONObject(0)?.getStringOrNull("name") .addPathSegment("chapters")
builder += MangaChapter( .build()
id = generateUid(id), val json = webClient.httpGet(url).parseJson().getJSONArray("data")
title = name, val builder = ChaptersListBuilder(json.length())
number = number, val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
volume = volume, val useBranching = config[splitTranslationsKey]
url = "${manga.url}/chapter?number=$numberString&volume=$volume", for (i in 0 until json.length()) {
scanlator = team, val jo = json.getJSONObject(i)
uploadDate = dateFormat.tryParse(bjo.getStringOrNull("created_at")), val volume = jo.getIntOrDefault("volume", 0)
branch = if (useBranching) team else null, val number = jo.getFloatOrDefault("number", 0f)
source = source, val numberString = number.formatSimple()
) val name = jo.getStringOrNull("name") ?: buildString {
} if (volume > 0) append("Том ").append(volume).append(' ')
} append("Глава ").append(numberString)
return builder.toList() }
} val branches = jo.getJSONArray("branches")
for (j in 0 until branches.length()) {
private suspend fun fetchTags(type: String): List<MangaTag> { val bjo = branches.getJSONObject(j)
val data = webClient.httpGet( val id = bjo.getLong("id")
HttpUrl.Builder() val team = bjo.getJSONArray("teams").optJSONObject(0)?.getStringOrNull("name")
.scheme("https") builder += MangaChapter(
.host("api.lib.social") id = generateUid(id),
.addPathSegment("api").addPathSegment(type).build(), title = name,
).parseJson().getJSONArray("data") number = number,
val prefix = type.first().toString() volume = volume,
return data.mapJSONNotNull { jo -> url = "${manga.url}/chapter?number=$numberString&volume=$volume",
val sites = jo.getJSONArray("site_ids").toIntSet() scanlator = team,
if (siteId !in sites) { uploadDate = dateFormat.tryParse(bjo.getStringOrNull("created_at")),
return@mapJSONNotNull null branch = if (useBranching) team else null,
} source = source,
MangaTag( )
title = jo.getString("name"), }
key = prefix + jo.getInt("id"), }
source = source, return builder.toList()
) }
}
} private suspend fun fetchTags(type: String): List<MangaTag> {
val data = webClient.httpGet(
private suspend fun fetchServers(): ScatterMap<String, String> { HttpUrl.Builder()
val json = webClient.httpGet( .scheme(SCHEME_HTTPS)
HttpUrl.Builder() .host(apiHost)
.scheme("https") .addPathSegment("api").addPathSegment(type).build(),
.host("api.lib.social") ).parseJson().getJSONArray("data")
.addPathSegment("api") val prefix = type.first().toString()
.addPathSegment("constants") return data.mapJSONNotNull { jo ->
.addQueryParameter("fields[]", "imageServers") val sites = jo.getJSONArray("site_ids").toIntSet()
.build(), if (siteId !in sites) {
).parseJson().getJSONObject("data").getJSONArray("imageServers") return@mapJSONNotNull null
val result = MutableScatterMap<String, String>() }
for (i in 0 until json.length()) { MangaTag(
val jo = json.getJSONObject(i) title = jo.getString("name"),
val sites = jo.getJSONArray("site_ids").toIntSet() key = prefix + jo.getInt("id"),
if (siteId !in sites) { source = source,
continue )
} }
result[jo.getString("id")] = jo.getString("url") }
}
return result private suspend fun fetchServers(): ScatterMap<String, String> {
} val json = webClient.httpGet(
HttpUrl.Builder()
private fun <V> IntObjectMap<V>.keyOf(value: V): Int { .scheme(SCHEME_HTTPS)
forEach { k, v -> .host(apiHost)
if (v == value) { .addPathSegment("api")
return k .addPathSegment("constants")
} .addQueryParameter("fields[]", "imageServers")
} .build(),
throw NoSuchElementException("No key associated with value $value") ).parseJson().getJSONObject("data").getJSONArray("imageServers")
} val result = MutableScatterMap<String, String>()
for (i in 0 until json.length()) {
private fun JSONArray.toIntSet(): IntSet { val jo = json.getJSONObject(i)
val result = MutableIntSet(length()) val sites = jo.getJSONArray("site_ids").toIntSet()
for (i in 0 until length()) { if (siteId !in sites) {
result.add(getInt(i)) continue
} }
return result result[jo.getString("id")] = jo.getString("url")
} }
return result
private fun MangaTag.typeKey() = when (key.firstOrNull()) { }
'g' -> "genres"
't' -> "tags" private fun <V> IntObjectMap<V>.keyOf(value: V): Int {
else -> throw IllegalArgumentException("Tag $key($title) is of unknown type") forEach { k, v ->
} if (v == value) {
return k
private fun tagsSetOf(tags: Collection<MangaTag>, genres: Collection<MangaTag>): Set<MangaTag> { }
val result = ArraySet<MangaTag>(tags.size + genres.size) }
val names = HashSet<String>(tags.size + genres.size) throw NoSuchElementException("No key associated with value $value")
genres.forEach { x -> if (names.add(x.title)) result.add(x) } }
tags.forEach { x -> if (names.add(x.title)) result.add(x) }
return result private fun JSONArray.toIntSet(): IntSet {
} val result = MutableIntSet(length())
for (i in 0 until length()) {
protected companion object { result.add(getInt(i))
}
const val SERVER_MAIN = "main" return result
const val SERVER_SECONDARY = "secondary" }
const val SERVER_COMPRESS = "compress"
const val SERVER_DOWNLOAD = "download" private fun MangaTag.typeKey() = when (key.firstOrNull()) {
const val SERVER_CROP = "crop" 'g' -> "genres"
} 't' -> "tags"
else -> throw IllegalArgumentException("Tag $key($title) is of unknown type")
}
private fun tagsSetOf(tags: Collection<MangaTag>, genres: Collection<MangaTag>): Set<MangaTag> {
val result = ArraySet<MangaTag>(tags.size + genres.size)
val names = HashSet<String>(tags.size + genres.size)
genres.forEach { x -> if (names.add(x.title)) result.add(x) }
tags.forEach { x -> if (names.add(x.title)) result.add(x) }
return result
}
private suspend fun getAuthData(): JSONObject? {
return JSONObject(WebViewHelper(context, domain).getLocalStorageValue("auth") ?: return null)
}
protected companion object {
const val SERVER_MAIN = "main"
const val SERVER_SECONDARY = "secondary"
const val SERVER_COMPRESS = "compress"
const val SERVER_DOWNLOAD = "download"
const val SERVER_CROP = "crop"
}
} }

@ -2,6 +2,10 @@ package org.koitharu.kotatsu.parsers.site.ru.rulib
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
@MangaSourceParser("MANGALIB", "MangaLib", "ru") @MangaSourceParser("MANGALIB", "MangaLib", "ru")
@ -11,5 +15,12 @@ internal class MangaLibParser(
context = context, context = context,
source = MangaParserSource.MANGALIB, source = MangaParserSource.MANGALIB,
siteId = 1, siteId = 1,
siteDomain = "test-front.mangalib.me", siteDomain = "mangalib.me",
) ) {
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> = try {
super.getPages(chapter)
} catch (e: NotFoundException) {
throw AuthRequiredException(source, e)
}
}

@ -9,5 +9,5 @@ internal class SlashLibParser(context: MangaLoaderContext) : LibSocialParser(
context = context, context = context,
source = MangaParserSource.YAOILIB, source = MangaParserSource.YAOILIB,
siteId = 2, siteId = 2,
siteDomain = "test-front.slashlib.me", siteDomain = "v2.slashlib.me",
) )

@ -30,6 +30,10 @@ public class FaviconParser(
links.mapNotNullTo(result) { link -> links.mapNotNullTo(result) { link ->
parseLink(link) parseLink(link)
} }
val touchIcons = doc.getElementsByAttributeValue("rel", "apple-touch-icon")
touchIcons.mapNotNullTo(result) { link ->
parseLink(link)
}
if (result.isEmpty()) { if (result.isEmpty()) {
result.add(createFallback()) result.add(createFallback())
} }

@ -0,0 +1,13 @@
package org.koitharu.kotatsu.parsers.util
import org.koitharu.kotatsu.parsers.MangaLoaderContext
public class WebViewHelper(
private val context: MangaLoaderContext,
private val domain: String,
) {
public suspend fun getLocalStorageValue(key: String): String? {
return context.evaluateJs("window.localStorage.getItem(\"$key\")")
}
}
Loading…
Cancel
Save