Luscious various fixes and updates (#6701)

* Update Luscious.kt

* Update LusciousGenerator.kt
This commit is contained in:
Johannes Joens 2021-04-26 00:24:46 +12:00 committed by GitHub
parent 7855257372
commit 19c274df1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 188 additions and 131 deletions

View File

@ -16,17 +16,11 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import rx.Observable
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
abstract class Luscious(
override val name: String,
@ -40,9 +34,7 @@ abstract class Luscious(
private val apiBaseUrl: String = "$baseUrl/graphql/nobatch/"
private val gson = Gson()
override val client: OkHttpClient = network.cloudflareClient
private val lusLang: String = lusLang(lang)
private fun lusLang(lang: String): String {
return when (lang) {
private val lusLang: String = when (lang) {
"en" -> "1"
"ja" -> "2"
"es" -> "3"
@ -55,8 +47,6 @@ abstract class Luscious(
"th" -> "101"
else -> "99"
}
}
// Common
@ -68,6 +58,8 @@ abstract class Luscious(
val tagsFilter = filters.findInstance<TagGroupFilter>()!!
val genreFilter = filters.findInstance<GenreGroupFilter>()!!
val contentTypeFilter = filters.findInstance<ContentTypeSelectFilter>()!!
val albumSizeFilter = filters.findInstance<AlbumSizeSelectFilter>()!!
val restrictGenresFilter = filters.findInstance<RestrictGenresSelectFilter>()!!
return JsonObject().apply {
add(
@ -85,6 +77,12 @@ abstract class Luscious(
if (albumTypeFilter.selected != FILTER_VALUE_IGNORE)
add(albumTypeFilter.toJsonObject("album_type"))
if (albumSizeFilter.selected != FILTER_VALUE_IGNORE)
add(albumSizeFilter.toJsonObject("picture_count_rank"))
if (restrictGenresFilter.selected != FILTER_VALUE_IGNORE)
add(restrictGenresFilter.toJsonObject("restrict_genres"))
with(interestsFilter) {
if (this.selected.isEmpty()) {
throw Exception("Please select an Interest")
@ -147,6 +145,22 @@ abstract class Luscious(
}
}
private fun buildAlbumInfoRequestInput(id: String): JsonObject {
return JsonObject().apply {
addProperty("id", id)
}
}
private fun buildAlbumInfoRequest(id: String): Request {
val input = buildAlbumInfoRequestInput(id)
val url = HttpUrl.parse(apiBaseUrl)!!.newBuilder()
.addQueryParameter("operationName", "AlbumGet")
.addQueryParameter("query", albumInfoQuery)
.addQueryParameter("variables", input.toString())
.toString()
return GET(url, headers)
}
// Latest
override fun latestUpdatesRequest(page: Int): Request = buildAlbumListRequest(page, getSortFilters(LATEST_DEFAULT_SORT_STATE))
@ -155,20 +169,47 @@ abstract class Luscious(
// Chapters
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
return listOf(
SChapter.create().apply {
url = response.request().url().toString()
name = "Chapter"
date_upload = document.select(".album-info-item:contains(Created:)")?.first()?.ownText()?.trim()?.let {
DATE_FORMATS_WITH_ORDINAL_SUFFIXES.mapNotNull { format -> format.parseOrNull(it) }.firstOrNull()?.time
} ?: 0L
chapter_number = 1f
}
)
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val id = manga.url.substringAfterLast("_").removeSuffix("/")
return client.newCall(GET(buildAlbumPicturesPageUrl(id, 1, "position")))
.asObservableSuccess()
.map { parseAlbumPicturesResponse(it, "position") }
}
private fun parseAlbumPicturesResponse(response: Response, sortPagesByOption: String): List<SChapter> {
val chapters = mutableListOf<SChapter>()
var nextPage = true
var page = 2
val id = response.request().url().queryParameter("variables").toString()
.let { gson.fromJson<JsonObject>(it)["input"]["filters"].asJsonArray }
.let { it.first { f -> f["name"].asString == "album_id" } }
.let { it["value"].asString }
var data = gson.fromJson<JsonObject>(response.body()!!.string())
.let { it["data"]["picture"]["list"].asJsonObject }
while (nextPage) {
nextPage = data["info"]["has_next_page"].asBoolean
data["items"].asJsonArray.map {
val chapter = SChapter.create()
chapter.url = it["url_to_original"].asString
chapter.name = it["title"].asString
//chapter.date_upload = it["created"].asLong // not parsing correctly for some reason
chapter.chapter_number = it["position"].asInt.toFloat()
chapters.add(chapter)
}
if (nextPage) {
val newPage = client.newCall(GET(buildAlbumPicturesPageUrl(id, page, sortPagesByOption))).execute()
data = gson.fromJson<JsonObject>(newPage.body()!!.string())
.let { it["data"]["picture"]["list"].asJsonObject }
}
page++
}
return chapters.reversed()
}
override fun chapterListParse(response: Response): List<SChapter> = throw UnsupportedOperationException("Not used")
// Pages
private fun buildAlbumPicturesRequestInput(id: String, page: Int, sortPagesByOption: String): JsonObject {
@ -203,43 +244,8 @@ abstract class Luscious(
.toString()
}
private fun parseAlbumPicturesResponse(response: Response, sortPagesByOption: String): List<Page> {
val id = response.request().url().queryParameter("variables").toString()
.let { gson.fromJson<JsonObject>(it)["input"]["filters"].asJsonArray }
.let { it.first { f -> f["name"].asString == "album_id" } }
.let { it["value"].asString }
val data = gson.fromJson<JsonObject>(response.body()!!.string())
.let { it["data"]["picture"]["list"].asJsonObject }
return data["items"].asJsonArray.mapIndexed { index, it ->
Page(index, imageUrl = it["thumbnails"][0]["url"].asString)
} + if (data["info"]["total_pages"].asInt > 1) { // get 2nd page onwards
(ITEMS_PER_PAGE until data["info"]["total_items"].asInt).chunked(ITEMS_PER_PAGE).mapIndexed { page, indices ->
indices.map { Page(it, url = buildAlbumPicturesPageUrl(id, page + 2, sortPagesByOption)) }
}.flatten()
} else emptyList()
}
private fun getAlbumSortPagesOption(chapter: SChapter): Observable<String> {
return client.newCall(GET(chapter.url))
.asObservableSuccess()
.map {
val sortByKey = it.asJsoup().select(".o-input-select:contains(Sorted By) .o-select-value")?.text() ?: ""
ALBUM_PICTURES_SORT_OPTIONS.getValue(sortByKey)
}
}
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
val id = chapter.url.substringAfterLast("_").removeSuffix("/")
return getAlbumSortPagesOption(chapter)
.concatMap { sortPagesByOption ->
client.newCall(GET(buildAlbumPicturesPageUrl(id, 1, sortPagesByOption)))
.asObservableSuccess()
.map { parseAlbumPicturesResponse(it, sortPagesByOption) }
}
return Observable.just(listOf(Page(0, chapter.url, chapter.url)))
}
override fun pageListParse(response: Response): List<Page> = throw UnsupportedOperationException("Not used")
@ -257,52 +263,47 @@ abstract class Luscious(
val data = gson.fromJson<JsonObject>(it.body()!!.string()).let { data ->
data["data"]["picture"]["list"].asJsonObject
}
data["items"].asJsonArray[page.index % 50].asJsonObject["thumbnails"][0]["url"].asString
data["items"].asJsonArray[page.index % 50].asJsonObject["url_to_original"].asString
}
}
// Details
private fun parseMangaGenre(document: Document): String {
return listOf(
document.select(".o-tag--secondary").map { it.text().substringBefore("(").trim() },
document.select(".o-tag:not([href *= /tags/artist])").map { it.text() },
document.select(".album-info-item:contains(Content:) .o-tag").map { it.text() }
).flatten().joinToString()
}
private fun parseMangaDescription(document: Document): String {
val pageCount: String? = (
document.select(".album-info-item:contains(pictures)").firstOrNull()
?: document.select(".album-info-item:contains(gifs)").firstOrNull()
)?.text()
return listOf(
Pair("Description", document.select(".album-description:last-of-type")?.text()),
Pair("Pages", pageCount)
).let {
it + listOf("Parody", "Character", "Ethnicity")
.map { key -> key to document.select(".o-tag--category:contains($key) .o-tag").joinToString { t -> t.text() } }
}.filter { desc -> !desc.second.isNullOrBlank() }
.joinToString("\n\n") { "${it.first}:\n${it.second}" }
override fun mangaDetailsRequest(manga: SManga): Request {
val id = manga.url.substringAfterLast("_").removeSuffix("/")
return buildAlbumInfoRequest(id)
}
override fun mangaDetailsParse(response: Response): SManga {
val document = response.asJsoup()
return SManga.create().apply {
artist = document.select(".o-tag--category:contains(Artist:) .o-tag")?.joinToString { it.text() }
author = artist
genre = parseMangaGenre(document)
title = document.select("a[title]").text()
status = when {
title.contains("ongoing", true) -> SManga.ONGOING
else -> SManga.COMPLETED
val data = gson.fromJson<JsonObject>(response.body()!!.string())
with(data["data"]["album"]["get"]) {
val manga = SManga.create()
manga.url = this["url"].asString
manga.title = this["title"].asString
manga.thumbnail_url = this["cover"]["url"].asString
manga.status = 0
manga.description = "${this["description"].asString}\n\nPictures: ${this["number_of_pictures"].asString}\nAnimated Pictures: ${this["number_of_animated_pictures"].asString}"
var genreList = this["language"]["title"].asString
for ((i, _) in this["labels"].asJsonArray.withIndex()) {
genreList = "$genreList, ${this["labels"][i].asString}"
}
for ((i, _) in this["genres"].asJsonArray.withIndex()) {
genreList = "$genreList, ${this["genres"][i]["title"].asString}"
}
for ((i, _) in this["audiences"].asJsonArray.withIndex()) {
genreList = "$genreList, ${this["audiences"][i]["title"].asString}"
}
for ((i, _) in this["tags"].asJsonArray.withIndex()) {
genreList = "$genreList, ${this["tags"][i]["text"].asString}"
if (this["tags"][i]["text"].asString.contains("Artist:")){
manga.artist = this["tags"][i]["text"].asString.substringAfter(":").trim()
manga.author = manga.artist
}
}
genreList = "$genreList, ${this["content"]["title"].asString}"
manga.genre = genreList
description = parseMangaDescription(document)
return manga
}
}
@ -371,6 +372,8 @@ abstract class Luscious(
class SortBySelectFilter(options: List<SelectFilterOption>, default: Int) : SelectFilter("Sort By", options, default)
class AlbumTypeSelectFilter(options: List<SelectFilterOption>) : SelectFilter("Album Type", options)
class ContentTypeSelectFilter(options: List<SelectFilterOption>) : SelectFilter("Content Type", options)
class RestrictGenresSelectFilter(options: List<SelectFilterOption>) : SelectFilter("Restrict Genres", options)
class AlbumSizeSelectFilter(options: List<SelectFilterOption>) : SelectFilter("Album Size", options)
override fun getFilterList(): FilterList = getSortFilters(POPULAR_DEFAULT_SORT_STATE)
@ -378,6 +381,8 @@ abstract class Luscious(
SortBySelectFilter(getSortFilters(), sortState),
AlbumTypeSelectFilter(getAlbumTypeFilters()),
ContentTypeSelectFilter(getContentTypeFilters()),
AlbumSizeSelectFilter(getAlbumSizeFilters()),
RestrictGenresSelectFilter(getRestrictGenresFilters()),
InterestGroupFilter(getInterestFilters()),
LanguageGroupFilter(getLanguageFilters()),
TagGroupFilter(getTagFilters()),
@ -391,8 +396,8 @@ abstract class Luscious(
SelectFilterOption("Rating - Last 30 Days", "rating_30_days"),
SelectFilterOption("Rating - Last 90 Days", "rating_90_days"),
SelectFilterOption("Rating - Last Year", "rating_1_year"),
SelectFilterOption("Rating - Last Year", "rating_1_year"),
SelectFilterOption("Date - Newest First", "date_newest"),
SelectFilterOption("Date - 2020", "date_2021"),
SelectFilterOption("Date - 2020", "date_2020"),
SelectFilterOption("Date - 2019", "date_2019"),
SelectFilterOption("Date - 2018", "date_2018"),
@ -405,7 +410,39 @@ abstract class Luscious(
SelectFilterOption("Date - Upcoming", "date_upcoming"),
SelectFilterOption("Date - Trending", "date_trending"),
SelectFilterOption("Date - Featured", "date_featured"),
SelectFilterOption("Date - Last Viewed", "date_last_interaction")
SelectFilterOption("Date - Last Viewed", "date_last_interaction"),
SelectFilterOption("First Letter - Any", "alpha_any"),
SelectFilterOption("First Letter - A", "alpha_a"),
SelectFilterOption("First Letter - B", "alpha_b"),
SelectFilterOption("First Letter - C", "alpha_c"),
SelectFilterOption("First Letter - D", "alpha_d"),
SelectFilterOption("First Letter - Any", "alpha_any"),
SelectFilterOption("First Letter - A", "alpha_a"),
SelectFilterOption("First Letter - B", "alpha_b"),
SelectFilterOption("First Letter - C", "alpha_c"),
SelectFilterOption("First Letter - D", "alpha_d"),
SelectFilterOption("First Letter - E", "alpha_e"),
SelectFilterOption("First Letter - F", "alpha_f"),
SelectFilterOption("First Letter - G", "alpha_g"),
SelectFilterOption("First Letter - H", "alpha_h"),
SelectFilterOption("First Letter - I", "alpha_i"),
SelectFilterOption("First Letter - J", "alpha_j"),
SelectFilterOption("First Letter - K", "alpha_k"),
SelectFilterOption("First Letter - L", "alpha_l"),
SelectFilterOption("First Letter - M", "alpha_m"),
SelectFilterOption("First Letter - N", "alpha_n"),
SelectFilterOption("First Letter - O", "alpha_o"),
SelectFilterOption("First Letter - P", "alpha_p"),
SelectFilterOption("First Letter - Q", "alpha_q"),
SelectFilterOption("First Letter - R", "alpha_r"),
SelectFilterOption("First Letter - S", "alpha_s"),
SelectFilterOption("First Letter - T", "alpha_t"),
SelectFilterOption("First Letter - U", "alpha_u"),
SelectFilterOption("First Letter - V", "alpha_v"),
SelectFilterOption("First Letter - W", "alpha_w"),
SelectFilterOption("First Letter - X", "alpha_x"),
SelectFilterOption("First Letter - Y", "alpha_y"),
SelectFilterOption("First Letter - Z", "alpha_z"),
)
fun getAlbumTypeFilters() = listOf(
@ -414,6 +451,12 @@ abstract class Luscious(
SelectFilterOption("Pictures", "pictures")
)
private fun getRestrictGenresFilters() = listOf(
SelectFilterOption("None", FILTER_VALUE_IGNORE),
SelectFilterOption("Loose", "loose"),
SelectFilterOption("Strict", "strict")
)
fun getContentTypeFilters() = listOf(
SelectFilterOption("All", FILTER_VALUE_IGNORE),
SelectFilterOption("Hentai", "0"),
@ -421,6 +464,17 @@ abstract class Luscious(
SelectFilterOption("Real People", "6")
)
private fun getAlbumSizeFilters() = listOf(
SelectFilterOption("All", FILTER_VALUE_IGNORE),
SelectFilterOption("0-25", "0"),
SelectFilterOption("0-50", "1"),
SelectFilterOption("50-100", "2"),
SelectFilterOption("100-200", "3"),
SelectFilterOption("200-800", "4"),
SelectFilterOption("800-3200", "5"),
SelectFilterOption("3200-12800", "6"),
)
fun getInterestFilters() = listOf(
CheckboxFilterOption("Straight Sex", "1"),
CheckboxFilterOption("Trans x Girl", "10", false),
@ -531,28 +585,8 @@ abstract class Luscious(
private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T
private fun SimpleDateFormat.parseOrNull(string: String): Date? {
return try {
parse(string)
} catch (e: ParseException) {
null
}
}
companion object {
private val ALBUM_PICTURES_SORT_OPTIONS = hashMapOf(
Pair("Sort By Newest", "date_newest"),
Pair("Sort By Rating", "rating_all_time")
).withDefault { "position" }
private const val ITEMS_PER_PAGE = 50
private val ORDINAL_SUFFIXES = listOf("st", "nd", "rd", "th")
private val DATE_FORMATS_WITH_ORDINAL_SUFFIXES = ORDINAL_SUFFIXES.map {
SimpleDateFormat("MMMM dd'$it', yyyy", Locale.US)
}
const val ENGLISH_LUS_LANG_VAL = "1"
const val JAPANESE_LUS_LANG_VAL = "2"
const val SPANISH_LUS_LANG_VAL = "3"
@ -594,8 +628,13 @@ abstract class Luscious(
total_pages
page
has_next_page
items_per_page
}
items {
created
title
url_to_original
position
thumbnails {
url
}
@ -604,5 +643,23 @@ abstract class Luscious(
}
}
""".replace("\n", " ").replace("\\s+".toRegex(), " ")
val albumInfoQuery = """
query AlbumGet(${"$"}id: ID!) {
album {
get(id: ${"$"}id) {
... on Album { ...AlbumStandard }
... on MutationError {
errors {
code message
}
}
}
}
}
fragment AlbumStandard on Album {
__typename id title labels description created modified like_status number_of_favorites number_of_dislikes rating moderation_status marked_for_deletion marked_for_processing number_of_pictures number_of_animated_pictures number_of_duplicates slug is_manga url download_url permissions cover { width height size url } created_by { id url name display_name user_title avatar { url size } } content { id title url } language { id title url } tags { category text url count } genres { id title slug url } audiences { id title url url } last_viewed_picture { id position url } is_featured featured_date featured_by { id url name display_name user_title avatar { url size } }
}
""".trimIndent()
}
}

View File

@ -10,7 +10,7 @@ class LusciousGenerator : ThemeSourceGenerator {
override val themeClass = "Luscious"
override val baseVersionCode: Int = 2
override val baseVersionCode: Int = 3
override val sources = listOf(
MultiLang("Luscious", "https://www.luscious.net", listOf("en","ja", "es", "it", "de", "fr", "zh", "ko", "other", "pt", "th"), isNsfw = true, className = "LusciousFactory", overrideVersionCode = 2),