[Multichan] Authorization support

pull/35/head
Koitharu 4 years ago
parent 9f75b90325
commit 79cdd18682
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -1,4 +1,4 @@
package org.koitharu.kotatsu.parsers.site package org.koitharu.kotatsu.parsers.site.grouple
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
@ -23,17 +23,17 @@ internal abstract class GroupleParser(source: MangaSource, userAgent: String) :
.build() .build()
override val sortOrders: Set<SortOrder> = EnumSet.of( override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED, SortOrder.UPDATED,
SortOrder.POPULARITY, SortOrder.POPULARITY,
SortOrder.NEWEST, SortOrder.NEWEST,
SortOrder.RATING, SortOrder.RATING,
) )
override suspend fun getList( override suspend fun getList(
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
val domain = getDomain() val domain = getDomain()
val doc = when { val doc = when {
@ -84,39 +84,40 @@ internal abstract class GroupleParser(source: MangaSource, userAgent: String) :
?: return@mapNotNull null ?: return@mapNotNull null
val tileInfo = descDiv.selectFirst("div.tile-info") val tileInfo = descDiv.selectFirst("div.tile-info")
val relUrl = href.toRelativeUrl(baseHost) val relUrl = href.toRelativeUrl(baseHost)
Manga( Manga(
id = generateUid(relUrl), id = generateUid(relUrl),
url = relUrl, url = relUrl,
publicUrl = href, publicUrl = href,
title = title, title = title,
altTitle = descDiv.selectFirst("h4")?.text(), altTitle = descDiv.selectFirst("h4")?.text(),
coverUrl = imgDiv.selectFirst("img.lazy")?.attr("data-original")?.replace("_p.", ".").orEmpty(), coverUrl = imgDiv.selectFirst("img.lazy")?.attr("data-original")?.replace("_p.", ".").orEmpty(),
rating = runCatching { rating = runCatching {
node.selectFirst("div.rating") node.selectFirst("div.rating")
?.attr("title") ?.attr("title")
?.substringBefore(' ') ?.substringBefore(' ')
?.toFloatOrNull() ?.toFloatOrNull()
?.div(10f) ?.div(10f)
}.getOrNull() ?: RATING_UNKNOWN, }.getOrNull() ?: RATING_UNKNOWN,
author = tileInfo?.selectFirst("a.person-link")?.text(), author = tileInfo?.selectFirst("a.person-link")?.text(),
isNsfw = false, isNsfw = false,
tags = runCatching { tags = runCatching {
tileInfo?.select("a.element-link") tileInfo?.select("a.element-link")
?.mapToSet { ?.mapToSet {
MangaTag( MangaTag(
title = it.text().toTitleCase(), title = it.text().toTitleCase(),
key = it.attr("href").substringAfterLast('/'), key = it.attr("href").substringAfterLast('/'),
source = source, source = source,
) )
} }
}.getOrNull().orEmpty(), }.getOrNull().orEmpty(),
state = when { state = when {
node.selectFirst("div.tags") node.selectFirst("div.tags")
?.selectFirst("span.mangaCompleted") != null -> MangaState.FINISHED ?.selectFirst("span.mangaCompleted") != null -> MangaState.FINISHED
else -> null
}, else -> null
source = source, },
) source = source,
)
} }
} }
@ -133,11 +134,11 @@ internal abstract class GroupleParser(source: MangaSource, userAgent: String) :
tags = manga.tags + root.select("div.subject-meta").select("span.elem_genre ") tags = manga.tags + root.select("div.subject-meta").select("span.elem_genre ")
.mapNotNull { .mapNotNull {
val a = it.selectFirst("a.element-link") ?: return@mapNotNull null val a = it.selectFirst("a.element-link") ?: return@mapNotNull null
MangaTag( MangaTag(
title = a.text().toTitleCase(), title = a.text().toTitleCase(),
key = a.attr("href").substringAfterLast('/'), key = a.attr("href").substringAfterLast('/'),
source = source, source = source,
) )
}, },
isNsfw = root.select(".alert-warning").any { it.ownText().contains(NSFW_ALERT) }, isNsfw = root.select(".alert-warning").any { it.ownText().contains(NSFW_ALERT) },
chapters = root.selectFirst("div.chapters-link")?.selectFirst("table") chapters = root.selectFirst("div.chapters-link")?.selectFirst("table")
@ -151,16 +152,16 @@ internal abstract class GroupleParser(source: MangaSource, userAgent: String) :
.replace("(Переводчик),", "&") .replace("(Переводчик),", "&")
.removeSuffix(" (Переводчик)") .removeSuffix(" (Переводчик)")
} }
MangaChapter( MangaChapter(
id = generateUid(href), id = generateUid(href),
name = tr.selectFirst("a")?.text().orEmpty().removePrefix(manga.title).trim(), name = tr.selectFirst("a")?.text().orEmpty().removePrefix(manga.title).trim(),
number = i + 1, number = i + 1,
url = href, url = href,
uploadDate = dateFormat.tryParse(tr.selectFirst("td.d-none")?.text()), uploadDate = dateFormat.tryParse(tr.selectFirst("td.d-none")?.text()),
scanlator = translators, scanlator = translators,
source = source, source = source,
branch = null, branch = null,
) )
}, },
) )
} }
@ -189,13 +190,13 @@ internal abstract class GroupleParser(source: MangaSource, userAgent: String) :
val page = pages.getJSONArray(i) val page = pages.getJSONArray(i)
val primaryServer = page.getString(0) val primaryServer = page.getString(0)
val url = page.getString(2) val url = page.getString(2)
MangaPage( MangaPage(
id = generateUid(url), id = generateUid(url),
url = "$primaryServer|$serversStr|$url", url = "$primaryServer|$serversStr|$url",
preview = null, preview = null,
referer = chapter.url, referer = chapter.url,
source = source, source = source,
) )
} }
} }
parseFailed("Pages list not found at ${chapter.url}") parseFailed("Pages list not found at ${chapter.url}")
@ -221,11 +222,11 @@ internal abstract class GroupleParser(source: MangaSource, userAgent: String) :
val root = doc.body().getElementById("mangaBox")?.selectFirst("div.leftContent") val root = doc.body().getElementById("mangaBox")?.selectFirst("div.leftContent")
?.selectFirst("table.table") ?: parseFailed("Cannot find root") ?.selectFirst("table.table") ?: parseFailed("Cannot find root")
return root.select("a.element-link").mapToSet { a -> return root.select("a.element-link").mapToSet { a ->
MangaTag( MangaTag(
title = a.text().toTitleCase(), title = a.text().toTitleCase(),
key = a.attr("href").substringAfterLast('/'), key = a.attr("href").substringAfterLast('/'),
source = source, source = source,
) )
} }
} }

@ -1,4 +1,4 @@
package org.koitharu.kotatsu.parsers.site package org.koitharu.kotatsu.parsers.site.grouple
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
@ -7,11 +7,11 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
@MangaSourceParser("MINTMANGA", "MintManga", "ru") @MangaSourceParser("MINTMANGA", "MintManga", "ru")
internal class MintMangaParser( internal class MintMangaParser(
override val context: MangaLoaderContext, override val context: MangaLoaderContext,
) : GroupleParser(MangaSource.MINTMANGA, "mintmangafun") { ) : GroupleParser(MangaSource.MINTMANGA, "mintmangafun") {
override val configKeyDomain = ConfigKey.Domain( override val configKeyDomain = ConfigKey.Domain(
"mintmanga.live", "mintmanga.live",
arrayOf("mintmanga.live", "mintmanga.com"), arrayOf("mintmanga.live", "mintmanga.com"),
) )
} }

@ -1,4 +1,4 @@
package org.koitharu.kotatsu.parsers.site package org.koitharu.kotatsu.parsers.site.grouple
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser

@ -1,4 +1,4 @@
package org.koitharu.kotatsu.parsers.site package org.koitharu.kotatsu.parsers.site.grouple
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
@MangaSourceParser("SELFMANGA", "SelfManga", "ru") @MangaSourceParser("SELFMANGA", "SelfManga", "ru")
internal class SelfMangaParser( internal class SelfMangaParser(
override val context: MangaLoaderContext, override val context: MangaLoaderContext,
) : GroupleParser(MangaSource.SELFMANGA, "selfmangafun") { ) : GroupleParser(MangaSource.SELFMANGA, "selfmangafun") {
override val configKeyDomain = ConfigKey.Domain("selfmanga.live", null) override val configKeyDomain = ConfigKey.Domain("selfmanga.live", null)

@ -1,13 +1,15 @@
package org.koitharu.kotatsu.parsers.site package org.koitharu.kotatsu.parsers.site.multichan
import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.exception.ParseException
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 java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
internal abstract class ChanParser(source: MangaSource) : MangaParser(source) { internal abstract class ChanParser(source: MangaSource) : MangaParser(source), MangaParserAuthProvider {
override val sortOrders: Set<SortOrder> = EnumSet.of( override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.NEWEST, SortOrder.NEWEST,
@ -15,6 +17,12 @@ internal abstract class ChanParser(source: MangaSource) : MangaParser(source) {
SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL,
) )
override val authUrl: String
get() = "https://${getDomain()}"
override val isAuthorized: Boolean
get() = context.cookieJar.getCookies(getDomain()).any { it.name == "dle_user_id" }
override suspend fun getList( override suspend fun getList(
offset: Int, offset: Int,
query: String?, query: String?,
@ -29,11 +37,13 @@ internal abstract class ChanParser(source: MangaSource) : MangaParser(source) {
} }
"https://$domain/?do=search&subaction=search&story=${query.urlEncoded()}" "https://$domain/?do=search&subaction=search&story=${query.urlEncoded()}"
} }
!tags.isNullOrEmpty() -> tags.joinToString( !tags.isNullOrEmpty() -> tags.joinToString(
prefix = "https://$domain/tags/", prefix = "https://$domain/tags/",
postfix = "&n=${getSortKey2(sortOrder)}?offset=$offset", postfix = "&n=${getSortKey2(sortOrder)}?offset=$offset",
separator = "+", separator = "+",
) { tag -> tag.key } ) { tag -> tag.key }
else -> "https://$domain/${getSortKey(sortOrder)}?offset=$offset" else -> "https://$domain/${getSortKey(sortOrder)}?offset=$offset"
} }
val doc = context.httpGet(url).parseHtml() val doc = context.httpGet(url).parseHtml()
@ -143,6 +153,14 @@ internal abstract class ChanParser(source: MangaSource) : MangaParser(source) {
} }
} }
override suspend fun getUsername(): String {
val doc = context.httpGet("https://${getDomain()}").parseHtml().body()
val root = doc.requireElementById("top_user")
val a = root.getElementsByAttributeValueContaining("href", "/user/").firstOrNull()
?: throw AuthRequiredException(source)
return a.attr("href").removeSuffix('/').substringAfterLast('/')
}
private fun getSortKey(sortOrder: SortOrder) = private fun getSortKey(sortOrder: SortOrder) =
when (sortOrder) { when (sortOrder) {
SortOrder.ALPHABETICAL -> "catalog" SortOrder.ALPHABETICAL -> "catalog"

@ -1,4 +1,4 @@
package org.koitharu.kotatsu.parsers.site package org.koitharu.kotatsu.parsers.site.multichan
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
@ -14,8 +14,8 @@ import org.koitharu.kotatsu.parsers.util.toTitleCase
internal class HenChanParser(override val context: MangaLoaderContext) : ChanParser(MangaSource.HENCHAN) { internal class HenChanParser(override val context: MangaLoaderContext) : ChanParser(MangaSource.HENCHAN) {
override val configKeyDomain = ConfigKey.Domain( override val configKeyDomain = ConfigKey.Domain(
"xxx.hentaichan.live", "y.hentaichan.live",
arrayOf("xxx.hentaichan.live", "xx.hentaichan.live", "hentaichan.live", "hentaichan.pro"), arrayOf("y.hentaichan.live", "xxx.hentaichan.live", "xx.hentaichan.live", "hentaichan.live", "hentaichan.pro"),
) )
override suspend fun getList( override suspend fun getList(
@ -34,8 +34,7 @@ internal class HenChanParser(override val context: MangaLoaderContext) : ChanPar
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {
val doc = context.httpGet(manga.url.toAbsoluteUrl(getDomain())).parseHtml() val doc = context.httpGet(manga.url.toAbsoluteUrl(getDomain())).parseHtml()
val root = val root = doc.body().getElementById("dle-content") ?: throw ParseException("Cannot find root")
doc.body().getElementById("dle-content") ?: throw ParseException("Cannot find root")
val readLink = manga.url.replace("manga", "online") val readLink = manga.url.replace("manga", "online")
return manga.copy( return manga.copy(
description = root.getElementById("description")?.html()?.substringBeforeLast("<div"), description = root.getElementById("description")?.html()?.substringBeforeLast("<div"),

@ -1,4 +1,4 @@
package org.koitharu.kotatsu.parsers.site package org.koitharu.kotatsu.parsers.site.multichan
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser

@ -1,4 +1,4 @@
package org.koitharu.kotatsu.parsers.site package org.koitharu.kotatsu.parsers.site.multichan
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
Loading…
Cancel
Save