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,48 +16,38 @@ 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 eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document
import rx.Observable import rx.Observable
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
abstract class Luscious( abstract class Luscious(
override val name: String, override val name: String,
override val baseUrl: String, override val baseUrl: String,
override val lang: String ) : HttpSource() { override val lang: String ) : HttpSource() {
//Based on Luscios single source extension form https://github.com/tachiyomiorg/tachiyomi-extensions/commit/aacf56d0c0ddb173372aac69d798ae998f178377 //Based on Luscios single source extension form https://github.com/tachiyomiorg/tachiyomi-extensions/commit/aacf56d0c0ddb173372aac69d798ae998f178377
//with modifiaction to make it support multisrc //with modifiaction to make it support multisrc
override val supportsLatest: Boolean = true override val supportsLatest: Boolean = true
private val apiBaseUrl: String = "$baseUrl/graphql/nobatch/" private val apiBaseUrl: String = "$baseUrl/graphql/nobatch/"
private val gson = Gson() private val gson = Gson()
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
private val lusLang: String = lusLang(lang) private val lusLang: String = when (lang) {
private fun lusLang(lang: String): String { "en" -> "1"
return when (lang) { "ja" -> "2"
"en" -> "1" "es" -> "3"
"ja" -> "2" "it" -> "4"
"es" -> "3" "de" -> "5"
"it" -> "4" "fr" -> "6"
"de" -> "5" "zh" -> "8"
"fr" -> "6" "ko" -> "9"
"zh" -> "8" "pt" -> "100"
"ko" -> "9" "th" -> "101"
"pt" -> "100" else -> "99"
"th" -> "101"
else -> "99"
}
} }
// Common // Common
private fun buildAlbumListRequestInput(page: Int, filters: FilterList, query: String = ""): JsonObject { private fun buildAlbumListRequestInput(page: Int, filters: FilterList, query: String = ""): JsonObject {
@ -68,6 +58,8 @@ abstract class Luscious(
val tagsFilter = filters.findInstance<TagGroupFilter>()!! val tagsFilter = filters.findInstance<TagGroupFilter>()!!
val genreFilter = filters.findInstance<GenreGroupFilter>()!! val genreFilter = filters.findInstance<GenreGroupFilter>()!!
val contentTypeFilter = filters.findInstance<ContentTypeSelectFilter>()!! val contentTypeFilter = filters.findInstance<ContentTypeSelectFilter>()!!
val albumSizeFilter = filters.findInstance<AlbumSizeSelectFilter>()!!
val restrictGenresFilter = filters.findInstance<RestrictGenresSelectFilter>()!!
return JsonObject().apply { return JsonObject().apply {
add( add(
@ -85,6 +77,12 @@ abstract class Luscious(
if (albumTypeFilter.selected != FILTER_VALUE_IGNORE) if (albumTypeFilter.selected != FILTER_VALUE_IGNORE)
add(albumTypeFilter.toJsonObject("album_type")) 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) { with(interestsFilter) {
if (this.selected.isEmpty()) { if (this.selected.isEmpty()) {
throw Exception("Please select an Interest") 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 // Latest
override fun latestUpdatesRequest(page: Int): Request = buildAlbumListRequest(page, getSortFilters(LATEST_DEFAULT_SORT_STATE)) override fun latestUpdatesRequest(page: Int): Request = buildAlbumListRequest(page, getSortFilters(LATEST_DEFAULT_SORT_STATE))
@ -155,20 +169,47 @@ abstract class Luscious(
// Chapters // Chapters
override fun chapterListParse(response: Response): List<SChapter> { override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val document = response.asJsoup() val id = manga.url.substringAfterLast("_").removeSuffix("/")
return listOf(
SChapter.create().apply { return client.newCall(GET(buildAlbumPicturesPageUrl(id, 1, "position")))
url = response.request().url().toString() .asObservableSuccess()
name = "Chapter" .map { parseAlbumPicturesResponse(it, "position") }
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
}
)
} }
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 // Pages
private fun buildAlbumPicturesRequestInput(id: String, page: Int, sortPagesByOption: String): JsonObject { private fun buildAlbumPicturesRequestInput(id: String, page: Int, sortPagesByOption: String): JsonObject {
@ -203,43 +244,8 @@ abstract class Luscious(
.toString() .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>> { override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
val id = chapter.url.substringAfterLast("_").removeSuffix("/") return Observable.just(listOf(Page(0, chapter.url, chapter.url)))
return getAlbumSortPagesOption(chapter)
.concatMap { sortPagesByOption ->
client.newCall(GET(buildAlbumPicturesPageUrl(id, 1, sortPagesByOption)))
.asObservableSuccess()
.map { parseAlbumPicturesResponse(it, sortPagesByOption) }
}
} }
override fun pageListParse(response: Response): List<Page> = throw UnsupportedOperationException("Not used") 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 -> val data = gson.fromJson<JsonObject>(it.body()!!.string()).let { data ->
data["data"]["picture"]["list"].asJsonObject 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 // Details
private fun parseMangaGenre(document: Document): String { override fun mangaDetailsRequest(manga: SManga): Request {
return listOf( val id = manga.url.substringAfterLast("_").removeSuffix("/")
document.select(".o-tag--secondary").map { it.text().substringBefore("(").trim() }, return buildAlbumInfoRequest(id)
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 mangaDetailsParse(response: Response): SManga { override fun mangaDetailsParse(response: Response): SManga {
val document = response.asJsoup() val data = gson.fromJson<JsonObject>(response.body()!!.string())
return SManga.create().apply { with(data["data"]["album"]["get"]) {
val manga = SManga.create()
artist = document.select(".o-tag--category:contains(Artist:) .o-tag")?.joinToString { it.text() } manga.url = this["url"].asString
author = artist manga.title = this["title"].asString
manga.thumbnail_url = this["cover"]["url"].asString
genre = parseMangaGenre(document) manga.status = 0
manga.description = "${this["description"].asString}\n\nPictures: ${this["number_of_pictures"].asString}\nAnimated Pictures: ${this["number_of_animated_pictures"].asString}"
title = document.select("a[title]").text() var genreList = this["language"]["title"].asString
status = when { for ((i, _) in this["labels"].asJsonArray.withIndex()) {
title.contains("ongoing", true) -> SManga.ONGOING genreList = "$genreList, ${this["labels"][i].asString}"
else -> SManga.COMPLETED
} }
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 SortBySelectFilter(options: List<SelectFilterOption>, default: Int) : SelectFilter("Sort By", options, default)
class AlbumTypeSelectFilter(options: List<SelectFilterOption>) : SelectFilter("Album Type", options) class AlbumTypeSelectFilter(options: List<SelectFilterOption>) : SelectFilter("Album Type", options)
class ContentTypeSelectFilter(options: List<SelectFilterOption>) : SelectFilter("Content 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) override fun getFilterList(): FilterList = getSortFilters(POPULAR_DEFAULT_SORT_STATE)
@ -378,6 +381,8 @@ abstract class Luscious(
SortBySelectFilter(getSortFilters(), sortState), SortBySelectFilter(getSortFilters(), sortState),
AlbumTypeSelectFilter(getAlbumTypeFilters()), AlbumTypeSelectFilter(getAlbumTypeFilters()),
ContentTypeSelectFilter(getContentTypeFilters()), ContentTypeSelectFilter(getContentTypeFilters()),
AlbumSizeSelectFilter(getAlbumSizeFilters()),
RestrictGenresSelectFilter(getRestrictGenresFilters()),
InterestGroupFilter(getInterestFilters()), InterestGroupFilter(getInterestFilters()),
LanguageGroupFilter(getLanguageFilters()), LanguageGroupFilter(getLanguageFilters()),
TagGroupFilter(getTagFilters()), TagGroupFilter(getTagFilters()),
@ -391,8 +396,8 @@ abstract class Luscious(
SelectFilterOption("Rating - Last 30 Days", "rating_30_days"), SelectFilterOption("Rating - Last 30 Days", "rating_30_days"),
SelectFilterOption("Rating - Last 90 Days", "rating_90_days"), SelectFilterOption("Rating - Last 90 Days", "rating_90_days"),
SelectFilterOption("Rating - Last Year", "rating_1_year"), SelectFilterOption("Rating - Last Year", "rating_1_year"),
SelectFilterOption("Rating - Last Year", "rating_1_year"),
SelectFilterOption("Date - Newest First", "date_newest"), SelectFilterOption("Date - Newest First", "date_newest"),
SelectFilterOption("Date - 2020", "date_2021"),
SelectFilterOption("Date - 2020", "date_2020"), SelectFilterOption("Date - 2020", "date_2020"),
SelectFilterOption("Date - 2019", "date_2019"), SelectFilterOption("Date - 2019", "date_2019"),
SelectFilterOption("Date - 2018", "date_2018"), SelectFilterOption("Date - 2018", "date_2018"),
@ -405,7 +410,39 @@ abstract class Luscious(
SelectFilterOption("Date - Upcoming", "date_upcoming"), SelectFilterOption("Date - Upcoming", "date_upcoming"),
SelectFilterOption("Date - Trending", "date_trending"), SelectFilterOption("Date - Trending", "date_trending"),
SelectFilterOption("Date - Featured", "date_featured"), 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( fun getAlbumTypeFilters() = listOf(
@ -414,6 +451,12 @@ abstract class Luscious(
SelectFilterOption("Pictures", "pictures") SelectFilterOption("Pictures", "pictures")
) )
private fun getRestrictGenresFilters() = listOf(
SelectFilterOption("None", FILTER_VALUE_IGNORE),
SelectFilterOption("Loose", "loose"),
SelectFilterOption("Strict", "strict")
)
fun getContentTypeFilters() = listOf( fun getContentTypeFilters() = listOf(
SelectFilterOption("All", FILTER_VALUE_IGNORE), SelectFilterOption("All", FILTER_VALUE_IGNORE),
SelectFilterOption("Hentai", "0"), SelectFilterOption("Hentai", "0"),
@ -421,6 +464,17 @@ abstract class Luscious(
SelectFilterOption("Real People", "6") 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( fun getInterestFilters() = listOf(
CheckboxFilterOption("Straight Sex", "1"), CheckboxFilterOption("Straight Sex", "1"),
CheckboxFilterOption("Trans x Girl", "10", false), 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 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 { 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 ENGLISH_LUS_LANG_VAL = "1"
const val JAPANESE_LUS_LANG_VAL = "2" const val JAPANESE_LUS_LANG_VAL = "2"
const val SPANISH_LUS_LANG_VAL = "3" const val SPANISH_LUS_LANG_VAL = "3"
@ -594,8 +628,13 @@ abstract class Luscious(
total_pages total_pages
page page
has_next_page has_next_page
items_per_page
} }
items { items {
created
title
url_to_original
position
thumbnails { thumbnails {
url url
} }
@ -604,5 +643,23 @@ abstract class Luscious(
} }
} }
""".replace("\n", " ").replace("\\s+".toRegex(), " ") """.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 themeClass = "Luscious"
override val baseVersionCode: Int = 2 override val baseVersionCode: Int = 3
override val sources = listOf( 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), MultiLang("Luscious", "https://www.luscious.net", listOf("en","ja", "es", "it", "de", "fr", "zh", "ko", "other", "pt", "th"), isNsfw = true, className = "LusciousFactory", overrideVersionCode = 2),