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