[Grouple] Authorization support
This commit is contained in:
@@ -5,6 +5,8 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Response
|
||||
import org.json.JSONArray
|
||||
import org.koitharu.kotatsu.parsers.MangaParser
|
||||
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
|
||||
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import org.koitharu.kotatsu.parsers.util.json.mapJSON
|
||||
@@ -16,24 +18,31 @@ private const val PAGE_SIZE_SEARCH = 50
|
||||
private const val NSFW_ALERT = "сексуальные сцены"
|
||||
private const val NOTHING_FOUND = "Ничего не найдено"
|
||||
|
||||
internal abstract class GroupleParser(source: MangaSource, userAgent: String) : MangaParser(source) {
|
||||
internal abstract class GroupleParser(source: MangaSource, userAgent: String) : MangaParser(source),
|
||||
MangaParserAuthProvider {
|
||||
|
||||
private val headers = Headers.Builder()
|
||||
.add("User-Agent", userAgent)
|
||||
.build()
|
||||
|
||||
override val sortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.POPULARITY,
|
||||
SortOrder.NEWEST,
|
||||
SortOrder.RATING,
|
||||
)
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.POPULARITY,
|
||||
SortOrder.NEWEST,
|
||||
SortOrder.RATING,
|
||||
)
|
||||
|
||||
override val authUrl: String
|
||||
get() = "https://grouple.co/internal/auth/login"
|
||||
|
||||
override val isAuthorized: Boolean
|
||||
get() = context.cookieJar.getCookies(getDomain()).any { it.name == "gwt" }
|
||||
|
||||
override suspend fun getList(
|
||||
offset: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
offset: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val domain = getDomain()
|
||||
val doc = when {
|
||||
@@ -45,18 +54,21 @@ internal abstract class GroupleParser(source: MangaSource, userAgent: String) :
|
||||
),
|
||||
headers,
|
||||
)
|
||||
|
||||
tags.isNullOrEmpty() -> context.httpGet(
|
||||
"https://$domain/list?sortType=${
|
||||
getSortKey(sortOrder)
|
||||
getSortKey(sortOrder)
|
||||
}&offset=${offset upBy PAGE_SIZE}",
|
||||
headers,
|
||||
)
|
||||
|
||||
tags.size == 1 -> context.httpGet(
|
||||
"https://$domain/list/genre/${tags.first().key}?sortType=${
|
||||
getSortKey(sortOrder)
|
||||
getSortKey(sortOrder)
|
||||
}&offset=${offset upBy PAGE_SIZE}",
|
||||
headers,
|
||||
)
|
||||
|
||||
offset > 0 -> return emptyList()
|
||||
else -> advancedSearch(domain, tags)
|
||||
}.parseHtml().body()
|
||||
@@ -84,40 +96,40 @@ internal abstract class GroupleParser(source: MangaSource, userAgent: String) :
|
||||
?: return@mapNotNull null
|
||||
val tileInfo = descDiv.selectFirst("div.tile-info")
|
||||
val relUrl = href.toRelativeUrl(baseHost)
|
||||
Manga(
|
||||
id = generateUid(relUrl),
|
||||
url = relUrl,
|
||||
publicUrl = href,
|
||||
title = title,
|
||||
altTitle = descDiv.selectFirst("h4")?.text(),
|
||||
coverUrl = imgDiv.selectFirst("img.lazy")?.attr("data-original")?.replace("_p.", ".").orEmpty(),
|
||||
rating = runCatching {
|
||||
node.selectFirst("div.rating")
|
||||
?.attr("title")
|
||||
?.substringBefore(' ')
|
||||
?.toFloatOrNull()
|
||||
?.div(10f)
|
||||
}.getOrNull() ?: RATING_UNKNOWN,
|
||||
author = tileInfo?.selectFirst("a.person-link")?.text(),
|
||||
isNsfw = false,
|
||||
tags = runCatching {
|
||||
tileInfo?.select("a.element-link")
|
||||
?.mapToSet {
|
||||
MangaTag(
|
||||
title = it.text().toTitleCase(),
|
||||
key = it.attr("href").substringAfterLast('/'),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}.getOrNull().orEmpty(),
|
||||
state = when {
|
||||
node.selectFirst("div.tags")
|
||||
?.selectFirst("span.mangaCompleted") != null -> MangaState.FINISHED
|
||||
Manga(
|
||||
id = generateUid(relUrl),
|
||||
url = relUrl,
|
||||
publicUrl = href,
|
||||
title = title,
|
||||
altTitle = descDiv.selectFirst("h4")?.text(),
|
||||
coverUrl = imgDiv.selectFirst("img.lazy")?.attr("data-original")?.replace("_p.", ".").orEmpty(),
|
||||
rating = runCatching {
|
||||
node.selectFirst("div.rating")
|
||||
?.attr("title")
|
||||
?.substringBefore(' ')
|
||||
?.toFloatOrNull()
|
||||
?.div(10f)
|
||||
}.getOrNull() ?: RATING_UNKNOWN,
|
||||
author = tileInfo?.selectFirst("a.person-link")?.text(),
|
||||
isNsfw = false,
|
||||
tags = runCatching {
|
||||
tileInfo?.select("a.element-link")
|
||||
?.mapToSet {
|
||||
MangaTag(
|
||||
title = it.text().toTitleCase(),
|
||||
key = it.attr("href").substringAfterLast('/'),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}.getOrNull().orEmpty(),
|
||||
state = when {
|
||||
node.selectFirst("div.tags")
|
||||
?.selectFirst("span.mangaCompleted") != null -> MangaState.FINISHED
|
||||
|
||||
else -> null
|
||||
},
|
||||
source = source,
|
||||
)
|
||||
else -> null
|
||||
},
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,11 +146,11 @@ internal abstract class GroupleParser(source: MangaSource, userAgent: String) :
|
||||
tags = manga.tags + root.select("div.subject-meta").select("span.elem_genre ")
|
||||
.mapNotNull {
|
||||
val a = it.selectFirst("a.element-link") ?: return@mapNotNull null
|
||||
MangaTag(
|
||||
title = a.text().toTitleCase(),
|
||||
key = a.attr("href").substringAfterLast('/'),
|
||||
source = source,
|
||||
)
|
||||
MangaTag(
|
||||
title = a.text().toTitleCase(),
|
||||
key = a.attr("href").substringAfterLast('/'),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
isNsfw = root.select(".alert-warning").any { it.ownText().contains(NSFW_ALERT) },
|
||||
chapters = root.selectFirst("div.chapters-link")?.selectFirst("table")
|
||||
@@ -152,16 +164,16 @@ internal abstract class GroupleParser(source: MangaSource, userAgent: String) :
|
||||
.replace("(Переводчик),", "&")
|
||||
.removeSuffix(" (Переводчик)")
|
||||
}
|
||||
MangaChapter(
|
||||
id = generateUid(href),
|
||||
name = tr.selectFirst("a")?.text().orEmpty().removePrefix(manga.title).trim(),
|
||||
number = i + 1,
|
||||
url = href,
|
||||
uploadDate = dateFormat.tryParse(tr.selectFirst("td.d-none")?.text()),
|
||||
scanlator = translators,
|
||||
source = source,
|
||||
branch = null,
|
||||
)
|
||||
MangaChapter(
|
||||
id = generateUid(href),
|
||||
name = tr.selectFirst("a")?.text().orEmpty().removePrefix(manga.title).trim(),
|
||||
number = i + 1,
|
||||
url = href,
|
||||
uploadDate = dateFormat.tryParse(tr.selectFirst("td.d-none")?.text()),
|
||||
scanlator = translators,
|
||||
source = source,
|
||||
branch = null,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -190,13 +202,13 @@ internal abstract class GroupleParser(source: MangaSource, userAgent: String) :
|
||||
val page = pages.getJSONArray(i)
|
||||
val primaryServer = page.getString(0)
|
||||
val url = page.getString(2)
|
||||
MangaPage(
|
||||
id = generateUid(url),
|
||||
url = "$primaryServer|$serversStr|$url",
|
||||
preview = null,
|
||||
referer = chapter.url,
|
||||
source = source,
|
||||
)
|
||||
MangaPage(
|
||||
id = generateUid(url),
|
||||
url = "$primaryServer|$serversStr|$url",
|
||||
preview = null,
|
||||
referer = chapter.url,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
parseFailed("Pages list not found at ${chapter.url}")
|
||||
@@ -222,14 +234,23 @@ internal abstract class GroupleParser(source: MangaSource, userAgent: String) :
|
||||
val root = doc.body().getElementById("mangaBox")?.selectFirst("div.leftContent")
|
||||
?.selectFirst("table.table") ?: parseFailed("Cannot find root")
|
||||
return root.select("a.element-link").mapToSet { a ->
|
||||
MangaTag(
|
||||
title = a.text().toTitleCase(),
|
||||
key = a.attr("href").substringAfterLast('/'),
|
||||
source = source,
|
||||
)
|
||||
MangaTag(
|
||||
title = a.text().toTitleCase(),
|
||||
key = a.attr("href").substringAfterLast('/'),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getUsername(): String {
|
||||
val root = context.httpGet("https://grouple.co/").parseHtml().body()
|
||||
val element = root.selectFirst("img.user-avatar") ?: throw AuthRequiredException(source)
|
||||
val res = element.parent()?.text()
|
||||
return if (res.isNullOrEmpty()) {
|
||||
parseFailed("Cannot find username")
|
||||
} else res
|
||||
}
|
||||
|
||||
private fun getSortKey(sortOrder: SortOrder) =
|
||||
when (sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "name"
|
||||
|
||||
Reference in New Issue
Block a user